Dear Dev-listers,
I've been working on re-writing the protocol specs for some time now. It's
still pretty raw, but I think good enough to share with you guys. Keep in
mind this is a "beta" release of the specs, at your own risk, yada yada.
It's not my fault if it this information breaks stuff or makes your dog
pregnant. :-) it's basically a re-write of the client.txt, based on
information passed on from our developers with and the kind assistance of
my cow-orker Paul Khafkine.
I would really appreciate any corrections or comments any of you may have,
and I hope this stirs up some useful discussion to increase our shared
understanding of the registration/lookup protocol.
Happy reading!
--Introduction ============
This document is offered as a description of the OpenSRS client/server API. This information may be useful if you wish to write your own client instead of the present Perl implementation in the Download Library: http://www.opensrs.org/src/.src.shtml
At present, the client code is in version 2.0.1: http://www.opensrs.org/src/current/opensrs-client-2.0.1.tar.gz
We recommend that you examine this client code if you have any questions. For the most part, the OpenSRS system is based upon the RRP protocol set out in RFC 2832 [NSI Registry Registrar Protocol (RRP) Version 1.1.0], except for specific OpenSRS-specific aspects of this implementations, including authentication and encryption. A copy of RFC 2832 can be found here: http://www.isi.edu/in-notes/rfc2832.txt
OpenSRS consists of two completely seperate subsystems: the registration system and the lookup system.
In the context of the OpenSRS, "client" refers to the OpenSRS client software scripts installed on the reseller's HTTP server. "Server" refers to the OpenSRS registration server (presently the hosts horizon.opensrs.net [test environment] and rr-n1-tor.opensrs.net [production environment]).
Version =======
This document is relevant to version 3.0 of the OpenSRS server protocol and version 2.0.1 of the client code.
Registration System ===================
Messages --------
Both client and server messages are sent in the same format. The first to bytes of a message are interpreted as a 16-bit, big-endian integer representing the length of the message. This two-byte header is not included in the length calculation. The rest of the message is plain ASCII text over the encryption layer. Specific message formats in the different connection states are described below.
Encryption ----------
The registration system requires that the client and server have previously agreed upon a private key for the client. This key serves to identify the client and is also used to create a session key for encrypting the session.
Connection State ----------------
When a client connects to the OpenSRS server, it is in the Authentication state. By providing appropriate credentials, the client may move into the Command state.
Authentication --------------
To begin the session, the client contacts the server by initiating a telnet session on port 50000. The server picks out the IP address of the incoming connection and checks it against the server allowable IP list. If the check fails, the server dumps the connection with the error message:
&Connection refused: invalid ip address
If the check passes, the server responds to the connection attempt by announcing the length of the text response to follow - it does this by prepending each line of data passed with with the length of the string, packed in "network" or big-endian order. We use Big-endian order instead of Little-endian because UNIX was traditionally designed for platforms with Big-endian hardware. This should not be a problem for Little-endian hardware, which can convert the data to Big-endian easier than vice-versa. In Perl, this is accomplished by:
$length = pack('n', length($data));
...where $data is the data you are going to send.
The client sends two separate print commands, one for the length of the data, and one for the actual data:
print SERVER pack('n', length($data)); print SERVER $data;
You could also place the string together in one print statement.
Note that because we always send the length of the string first, it will not work to simply telnet to the OpenSRS server on port 50000 and begin issuing commands. You must either use our code or write your own which accomplishes the same end.
Next, the client unpacks the first two bytes (if your system is Big-endian, no conversion is neccessary, otherwise it needs to converted to Little-endian). This value <len> can assist in parsing the client/server through. especially if you're using a language like C.
Then the server will send: OpenSRS SERVER 3.2: READY
The version number of the server software may change (at the time of this writing it is 3.2).
Note that for this session the first two bytes equaled 25 decimal corresponding to the 25-character string "OpenSRS SERVER 3.2: READY" passed to the client.
Once the client receives this (indicating the server is ready) the client sends: OpenSRS CLIENT 3.2\n
Client messages are terminated with a linefeed character ("\n"). The version number the the client software may vary depending on the client code version.
The server sends "OpenSRS SERVER <version>" where version is the current protocol version. In response, the client sends "OpenSRS CLIENT <version>" where version is the client's protocol version. Note that this number should not be changed. It is used to allow for API changes and backward compatibility. If you change the <version> of the client things will probably break.
The client sends two bytes defining the number of characters to follow.
The client then sends: login -<crypt_type> <username>
Earlier protocol specifications had a dash ("-") before the <username> but this was a typo. The <crypt_type> is either "des" or "blowfish", and <username> is the reseller's username.
Now both sides start up their encryption ciphers using the reseller's private key, but no data passes between the server and client yet. For more info on the encryption process, please see the section below on encryption.
While this is happening, the server performs a security cross-check, further to the original check against the server's allowable IPs list. The client IP address must also match the IP address or range recorded in the server's database for that specific username. Clever, eh?
If this check fails, the server drops the connection with the return code:
410 reseller authentication error
If the check passes, the server will encrypt a randomly-generated string using the PRIVATE_KEY that was generated on the server beforehand for the RSP's account. This PRIVATE_KEY is also stored on the client side in the OpenSRS.conf file. (This process assumes that the PRIVATE_KEY in the client's OpenSRS.conf and in the server's database are identical in order for this to work.)
Note: The private and test environments are on different machines. Remember that the private key you generate on the test environment Web interface http://resellers-test.opensrs.net/ will only work on the test environment server horizon.opensrs.net. Likewise, the private key you generate on the production environment Web interface http://resellers-test.opensrs.net/ will only work on the production environment server rr-n1-tor.opensrs.net.
The client then reads in the challenge, decrypts it using the local copy of PRIVATE_KEY in the OpenSRS.conf file, runs it through Digest::MD5::md5() to make an MD5 hash (a mathematical "fingerprint of the string), and sends back the result in encrypted form. This MD5 hash is sent to the server as a response to the original challenge.
The server will then decrypt the MD5 hash, comparing it to the MD5 hash of the original random string.
The server then reads back the result, verifies the data, and returns: 200 Authentication Successful
...if the data matched. Otherwise, the connection is closed.
If the connection passes authentication, the connection moves into the Command state. Any other status code indicates failure and the connection is terminated. If the connection passes to the Command state, the session will then continue in this pattern: the client sends a command, the server responds with a return code. The client commands and the server return codes will be encrypted using the established cipher.
{Developer testing indicates that the server terminates the connection without sending a status message in the event of failed authentication. This is bad behavior on the part of the server, and furthermore not the documented behavior.}
Commands --------
Commands are sent as a single line terminated by a linefeed character. A command consists of a series of key/value pairs, in query string format. That is, values are first URL-encoded as per RFC xxxx, then the pairs are combined in the format: {What RFC were the developers talking about here?}
key1=value1[&key2=value2...&keyN=valueN]
If a given key is to hold more than one value, each additional values is appended to the existing value string, separated by a null character ("\0"). The following keys are required to be present in the command:
'action' is the command to be executed; '_registrant_ip' is the IP address of the end user. Note that this is not necessarily the IP address of the client. Most likely, it is the address of web browser being used.
...other valid keys depend on the command being executed.
Responses are sent as one or two lines, each terminated by a linefeed character {is this correct?}. The first line is a status message as described above. The second (optional) line consists of key/value pairs as described in the previous paragraph.
{Why aren't keys also encoded? For currently valid keys this would make no difference, but there doesn't seem to be any good reason to send them raw.}
A client/server communication transcript in pseudocode ======================================================
This is a generic transcript of the client/server communication. This transcript does not make any reference to a specific programming language, and the functions used are simply pseudocode to indicate the actions performed by the client/server before exchanging data.
General pseudocode assumptions:
<len> is the length of the string to follow in big-endian (network) order
encrypt() and decrypt() are generic language-independent functions indicating that the data being sent is encrypted.
MD5() is a generic language-independent function indicating that the data being sent is passed through an MD5 Digest cryptography algorithm.
server: <len> OpenSRS SERVER 3.2: READY
client: <len> OpenSRS CLIENT 3.2 client: <len> login -blowfish testuser
server: <len> encrypt(ramdom_string)
client: <len> encrypt(MD5(decrypt(random_string)))
server: <len> encrypt(200 Authentication Successful)
Everything after that point looks like this:
client: <len> encrypt(command)
server: <len> encrypt(return code)
Commands and syntax, return codes:
Encryption ==========
The encryption algorithims DES and Blowfish are currently supported.
The suggested method of using these encryption types is through their respective Perl modules, Crypt::DES and Crypt::Blowfish, which are then accessed through a common interface created by Crypt::CBC. Crypt::CBC is now included in the OpenSRS client distribution.
While DES only supports keys of 8 bytes, it has been scrutinized for many years and has held up very well. Blowfish supports much larger keys (up to 56 bytes) but is relatively new on the encryption scene. It is holding up well under examination and is slowly being adopted by various vendors. (OpenBSD, known for their attention to security, now uses the Blowfish algorithim for encrypting system passwords.)
For those people who are unable to install Crypt::DES or Crypt::Blowfish there is a third option available: Crypt::Blowfish_PP which is a module for Blowfish written in Pure Perl (PP). Our initial testing has shown this module to be at least 10 times slower than the standard Crypt::Blowfish but it may be used as a last resort.
Another note of interest relates to the length of the private key that is used. Starting with this client release, private keys will be 112 characters in length to provide the maximum security for people using Blowfish. Clients who continue to use DES will not be affected as Crypt::CBC only uses the portion of the private key that is needed. DES, therefore, simply ignores everything after the first 16 characters of the key.
Note for existing resellers: ----------------------------
If you wish to use Crypt::Blowfish and you are an existing customer, it is recommended that you generate a new private key to ensure the strongest encryption possible for this module. Old keys were 8 bytes in length and new keys are 56 bytes in length.
If you wish to write your own client, the following information may be of help:
When creating your encryption cipher, do not use the private key in raw form. Instead, first pack the key into a hexadecimal binary string. In perl this is accomplished with: $private_key = pack('H*', $private_key);
You may then use the new private key to create your encryption cipher and begin sending data.
Note for WIN32 users: ---------------------
With this client release it should be possible to get up and running on a Windows-based platform (Windows NT 4 or Windows 2000) with little trouble. If you are unable to compile Crypt::DES or Crypt::Blowfish, you are free to experiment with Crypt::Blowfish_PP (a "Pure Perl" implementation). While slower than the standard Crypt::Blowfish module, Crypt::Blowfish_PP requires no compilation. We do not recommend and we will not support attempts to implement the client software on Windows 3.11/95/98 or Windows NT prior to version 4.0.
This client release also includes Crypt::CBC so the only additional Perl module that is required is Digest::MD5.
Status messages (return codes) ==============================
Both the lookup and registration subsystems use the following format to return the status of a request. The format is: <code> <text>
...where <code> is a three-digit, machine-readable status code, and <text> is a human-readable description of the code. Successful requests will result in a 2** status being returned. Unsuccessfull requests will result in a 4** status. More specific codes are defined for certain commands. Return Codes: -------------
# if the command completed with no errors 200 Command successful
# internal errors 400 internal server error 405 registry error
# authentication errors 410 reseller authentication error 415 registrant (end-user) authentication error
# command errors 430 invalid command 435 permission denied subuser permissions # quota errors 440 exceeded registration quota 445 exceeded nameserver quota 447 exceeded subuser quota
# data errors 460 missing required field 465 invalid data supplied domain syntax, contact info, duplicate nameservers, etc. # request errors 480 item not found (domain, nameserver, profile, etc) 485 entity already exists (nameserver, domain, subuser, etc. - e.g. domain taken) 487 domain not transferrable
Lookup System =============
The lookup system works similarly to the registration system.
{Does the lookup system start up after the registration system authenticates, using the same encryption scheme?}
Connections to the lookup system are on port 51000.
Messages --------
Messages are plain ASCII text (over the encryption layer). Client messages consist of a single line in the format: <command> [<parameters> ... ]
Client messages are terminated with a linefeed character ("\n").
Server messages are multi-line. The first line is a status message in the format above. The status line is followed by zero or more lines of data. The message is terminated by a line containing a single period. Each line of a server message is terminated with a carriage return - linefeed combination ("\r\n"); {Apparently server messages can now be terminated with just a carriage return - did the developers fix this inconsistency?}
Why are client messages and server messages terminated differently? The Perl source uses this line to send the lookup command:
print $fh "check_domain $domain $username $affiliate_id\n";
I suspect that this is an implementation bug in the Perl library.
Connection State ----------------
The only state of a lookup connection is the command state, described below.
Commands --------
The only valid client command on the lookup system is "check_domain". The format for this command is:
check_domain <domain> [<user_id> [<affiliate_id>] ]
...where: <domain> is the domain name to be looked up <user_id> is the reseller's OpenSRS ID <affiliate_id> is the (optional) affiliate ID, if the reseller wants to use this field to track registrations
The following status codes are returned by the server: 210 Domain name available 211 Domain name not available 505 Invalid attribute value syntax 5000 Invalid command name.
I suspect that the 5000 code above is an implementation bug in the current server implementation (it should be 500).
--
William Porquet, MA TUCOWS/OpenSRS Tech Ops mailto:william@tucows.com http://www.tucows.com/ "Measure twice. Cut once." - old carpenter's saying
This archive was generated by hypermail 2.1.3 : Tue Oct 19 2004 - 23:35:36 EDT