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:

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):

  1. User enters the password pass
  2. 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"} ]
    
  3. Remote application takes the SHA-1 hash of the password entered:
    password_hash = sha.sha("pass").hexdigest()
    (Value of password_hash = 9d4e1e23bd5b727046a9e3b4b7db57bd8d6ee684)
  4. Remote application bencodes the commands array:
    bencoded_commands = bencode(mydict["commands"])
    (Value of commands_bencoded = ld4:name5:close6:target3:abced4:name5:close6:target3:abcee)
  5. 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)
  6. 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)
  7. 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)
  8. 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.):

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):

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.


The return response will be in the following form:


The following are valid commands and their results:

Close

Tell ABC to close either itself or the web service.

Result:

No result returned (since the web service will no longer be active to return the result)


Query

Get current status of torrents.

Result:


Version

Returns the version information for ABC

Result:


Delete

Delete either completed torrents or a list of torrents (by infohash)

Result:


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.

Result:


Resume

Resume either all stopped torrents, or indidivual torrents

Result:


Pause

Pause some or all of the torrents in the list.

Result:


Unpause

Unpause some or all of the torrents in the list.

Result:


Stop

Stop all or some of the torrents in the list.

Result:


Queue

Queue all or some torrents in the list.

Result:


Super-seed

Enable super-seed for all or some seeding torrents in the list.

Result:


Move

Move torrents in the list.

Result:


Priority

Change the priority of torrents.

Result:


Get String

Get the values for strings in the current language file

Result:


Set String

Set the value for a string in the user language file

Result:


Get Param

Get the values for parameters in the config file

Result:


Set Param

Set the values for parameters in the config file (note: changing some values may require torrents to be restarted to take effect)

Result:


Get Torrent Param

Get the values for parameters for one or more torrents

Result:


Set Torrent Param

Set the values for parameters for one or more torrents

Result: