/**
* OnionCoffee - Anonymous Communication through TOR Network
* Copyright (C) 2005-2007 RWTH Aachen University, Informatik IV
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* 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
*/
package TorJava;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import TorJava.Common.Encoding;
import TorJava.Common.TorException;
/**
* the general form of a RELAY cell in the Tor Protocol. This class also calls
* the crypto- functions in Node.java to decode an onion, if encrypted data is
* received.
*
* @author Lexi Pimenidis
* @version unstable
*/
public class CellRelay extends Cell {
public byte relay_command;
// byte[] recognized; (ALWAYS ZERO)
int streamID; // 16 bit unsigned integer
byte[] digest;
int length; // 16 bit unsigned integer
public byte[] data;
/** set to a value from 0 to outCircuit.route_established-1
* to address a special router in the chain, default is the last one
*/
private int addressedRouterInCircuit=-1;
static final byte CELL_RELAY_ESTABLISH_RENDEZVOUS = 33;
static final byte CELL_RELAY_INTRODUCE1 = 34;
static final byte CELL_RELAY_RENDEZVOUS2 = 37;
static final byte CELL_RELAY_RENDEZVOUS_ESTABLISHED = 39;
static final byte CELL_RELAY_COMMAND_INTRODUCE_ACK = 40;
TCPStream s;
static final int RELAY_BEGIN = 1;
static final int RELAY_DATA = 2;
public static final int RELAY_END = 3;
static final int RELAY_CONNECTED = 4;
static final int RELAY_SENDME = 5;
static final int RELAY_EXTEND = 6;
static final int RELAY_EXTENDED = 7;
static final int RELAY_TRUNCATE = 8;
static final int RELAY_TRUNCATED = 9;
static final int RELAY_DROP = 10;
static final int RELAY_RESOLVE = 11;
static final int RELAY_RESOLVED = 12;
static final int RELAY_ESTABLISH_INTRO = 32;
static final int RELAY_ESTABLISH_RENDEVOUS = 33;
static final int RELAY_INTRODUCE1 = 34;
static final int RELAY_INTRODUCE2 = 35;
static final int RELAY_RENDEVOUS1 = 36;
static final int RELAY_RENDEVOUS2 = 37;
static final int RELAY_INTRO_ESTABLISHED = 38;
static final int RELAY_RENDEVOUS_ESTABLISHED = 39;
static final int RELAY_COMMAND_INTRODUCE_ACK = 40; // FIX there are some
// constant declared
// twice
static final int RELAY_COMMAND_SIZE = 1;
static final int RELAY_RECOGNIZED_SIZE = 2;
static final int RELAY_STREAMID_SIZE = 2;
static final int RELAY_DIGEST_SIZE = 4;
static final int RELAY_LENGTH_SIZE = 2;
static final int RELAY_DATA_SIZE = 498;
static final int RELAY_TOTAL_SIZE = CELL_PAYLOAD_SIZE;
static final int RELAY_COMMAND_POS = 0;
static final int RELAY_RECOGNIZED_POS = RELAY_COMMAND_POS
+ RELAY_COMMAND_SIZE;
static final int RELAY_STREAMID_POS = RELAY_RECOGNIZED_POS
+ RELAY_RECOGNIZED_SIZE;
static final int RELAY_DIGEST_POS = RELAY_STREAMID_POS
+ RELAY_STREAMID_SIZE;
static final int RELAY_LENGTH_POS = RELAY_DIGEST_POS + RELAY_DIGEST_SIZE;
static final int RELAY_DATA_POS = RELAY_LENGTH_POS + RELAY_LENGTH_SIZE;
// DEBUG_START
/** used for a nicer debugging output */
static final String[] command_to_string = { "zero", "begin", "data", "end",
"connected", "sendme", "extend", "extended", "truncate",
"truncated", "drop", "resolv", "resolved" };
static final String[] reason_to_string = { "none", "misc",
"resolve failed", "connect refused", "exit policy", "destroy",
"done", "timeout", "(unallocated - see spec)", "hibernating",
"internal", "resource limit", "connection reset",
"tor protocol violation" };
// DEBUG_END
/**
* constructor. used for EXTEND-cells and SENDME-cells
*/
CellRelay(Circuit c, int relay_command) {
super(c, Cell.CELL_RELAY);
this.relay_command = (byte) relay_command;
reset();
}
/**
* init cell. used by RELAY_BEGIN-cells
*/
CellRelay(TCPStream s, int relay_command) {
super(s.circ, Cell.CELL_RELAY);
reset();
this.s = s;
this.streamID = s.ID;
this.relay_command = (byte) relay_command;
}
/**
* gets memory, resets values
*/
void reset() {
streamID = 0;
digest = new byte[4];
length = 0;
data = new byte[498];
}
/**
* init from received data
*/
CellRelay(byte[] data) throws TorException {
super(data);
reset();
init_from_data();
}
/**
* init from main Cell-type
*/
CellRelay(Circuit circ, Cell cell) throws TorException {
super(cell.toByteArray()); // TODO: inefficient at max! - but currently working
reset();
this.outCircuit = circ;
init_from_data();
}
/**
* init cell from stream
*/
CellRelay(InputStream in) throws IOException, TorException {
super(in);
reset();
init_from_data();
}
boolean isTypeBegin() {
return relay_command == RELAY_BEGIN;
}
boolean isTypeData() {
return relay_command == RELAY_DATA;
}
boolean isTypeEnd() {
return relay_command == RELAY_END;
}
boolean isTypeConnected() {
return relay_command == RELAY_CONNECTED;
}
boolean isTypeSendme() {
return relay_command == RELAY_SENDME;
}
boolean isTypeExtend() {
return relay_command == RELAY_EXTEND;
}
boolean isTypeExtended() {
return relay_command == RELAY_EXTENDED;
}
boolean isTypeTruncate() {
return relay_command == RELAY_TRUNCATE;
}
boolean isTypeTruncated() {
return relay_command == RELAY_TRUNCATED;
}
boolean isTypeDrop() {
return relay_command == RELAY_DROP;
}
boolean isTypeResolve() {
return relay_command == RELAY_RESOLVED;
}
boolean isTypeResolved() {
return relay_command == RELAY_RESOLVED;
}
boolean isTypeEstablishedRendezvous() {
return relay_command == CELL_RELAY_RENDEZVOUS_ESTABLISHED;
}
boolean isTypeIntroduceACK() {
return relay_command == CELL_RELAY_COMMAND_INTRODUCE_ACK;
}
boolean isTypeRendezvous2() {
return relay_command == CELL_RELAY_RENDEZVOUS2;
}
boolean isTypeIntroduce2() {
return relay_command == RELAY_INTRODUCE2;
}
/**
* decrypts an onion and checks digests and stuff. input is taken from the
* parent class' payload.
*/
void init_from_data() throws TorException {
Logger.logCell(Logger.VERBOSE, "CellRelay.init_from_data() for "
+ outCircuit.route_established + " layers");
// decrypt forwards, take keys from route
int encrypting_router;
boolean digest_verified = false;
if (outCircuit.route_established == 0)
Logger.logCell(Logger.WARNING,
"CellRelay.init_from_data() for zero layers on "
+ outCircuit.print());
for (encrypting_router = 0; encrypting_router <= outCircuit.route_established; ++encrypting_router) {
// check if no decryption has lead to a recognized cell
if (encrypting_router == outCircuit.route_established)
throw new TorException(
"relay cell not recognized, possibly due to decryption errors?");
// decrypt payload
outCircuit.route[encrypting_router].sym_decrypt(payload);
// if recognized and digest is correct, then stop decrypting
if ((payload[CellRelay.RELAY_RECOGNIZED_POS] == 0)
&& (payload[CellRelay.RELAY_RECOGNIZED_POS + 1] == 0)) {
// check digest.
System.arraycopy(payload, CellRelay.RELAY_DIGEST_POS, digest,
0, CellRelay.RELAY_DIGEST_SIZE); // save digest
payload[CellRelay.RELAY_DIGEST_POS] = 0; // set to ZERO
payload[CellRelay.RELAY_DIGEST_POS + 1] = 0;
payload[CellRelay.RELAY_DIGEST_POS + 2] = 0;
payload[CellRelay.RELAY_DIGEST_POS + 3] = 0;
byte[] digest_calc = outCircuit.route[encrypting_router]
.calc_backward_digest(payload); // calc digest
System
.arraycopy(digest, 0, payload,
CellRelay.RELAY_DIGEST_POS,
CellRelay.RELAY_DIGEST_SIZE); // restore
// digest
if ((digest[0] == digest_calc[0]) && // check digest
(digest[1] == digest_calc[1])
&& (digest[2] == digest_calc[2])
&& (digest[3] == digest_calc[3])) {
Logger.logCell( Logger.VERBOSE,
"CellRelay.init_from_data(): backward digest from "
+ outCircuit.route[encrypting_router].server.nickname
+ " is OK");
digest_verified = true;
break;
}
;
}
;
}
// check if digest verified
if (!digest_verified) {
Logger.logCell(Logger.WARNING,
"CellRelay.init_from_data(): Received "
+ Encoding.toHexString(digest)
+ " as backward digest but couldn't verify");
throw new TorException("wrong digest");
}
;
// copy data from payload
relay_command = payload[CellRelay.RELAY_COMMAND_POS];
streamID = Encoding.byteArrayToInt(payload, CellRelay.RELAY_STREAMID_POS,
CellRelay.RELAY_STREAMID_SIZE);
length = Encoding.byteArrayToInt(payload, CellRelay.RELAY_LENGTH_POS,
CellRelay.RELAY_LENGTH_SIZE);
System.arraycopy(payload, CellRelay.RELAY_DATA_POS, data, 0,
CellRelay.RELAY_DATA_SIZE);
// DEBUG OUTPUT
Logger.logCell(Logger.VERBOSE, "CellRelay.init_from_data(): "
+ print());
}
/** set to a value from 0 to outCircuit.route_established-1
* to address a special router in the chain, default is the last one
*/
boolean setAddressedRouter(int router) {
if ((router>-1)&&(router < outCircuit.route_established)) {
addressedRouterInCircuit = router;
return true;
};
return false;
}
/**
* prepares the meta-data, such that the cell can be transmitted. encrypts
* an onion.
*
* @return the data ready for sending
*/
byte[] toByteArray() {
Logger.logCell(Logger.VERBOSE, "CellRelay.toByteArray() for "
+ outCircuit.route_established + " layers");
// put everything in payload
payload[CellRelay.RELAY_COMMAND_POS] = (byte) relay_command;
System.arraycopy(Encoding.intToNByteArray(streamID,
CellRelay.RELAY_STREAMID_SIZE), 0, payload,
CellRelay.RELAY_STREAMID_POS, CellRelay.RELAY_STREAMID_SIZE);
System.arraycopy(Encoding.intToNByteArray(length,
CellRelay.RELAY_LENGTH_SIZE), 0, payload,
CellRelay.RELAY_LENGTH_POS, CellRelay.RELAY_LENGTH_SIZE);
System.arraycopy(data, 0, payload, CellRelay.RELAY_DATA_POS,
CellRelay.RELAY_DATA_SIZE);
// calc digest and insert it
int i0 = outCircuit.route_established - 1;
if (addressedRouterInCircuit>=0) i0=addressedRouterInCircuit;
digest = outCircuit.route[i0].calc_forward_digest(payload);
System.arraycopy(digest, 0, payload, CellRelay.RELAY_DIGEST_POS,
CellRelay.RELAY_DIGEST_SIZE);
// DEBUG OUTPUT
Logger.logCell(Logger.RAW_DATA, "CellRelay.toByteArray(): " + print());
// encrypt backwards, take keys from route
for (int i = i0; i >= 0; --i)
outCircuit.route[i].sym_encrypt(payload);
// create the byte array to be send over TLS
return super.toByteArray();
}
// DEBUG_START
/**
* for debugging and stuff
*/
static String reasonForClosing(int reason) {
if ((reason < 0) || (reason >= reason_to_string.length))
return "[" + reason + "]";
return reason_to_string[reason];
}
public String reasonForClosing() {
return reasonForClosing(data[0]);
}
public String relayCommand() {
return relayCommand(relay_command);
}
public static String relayCommand(int cmd) {
if ((cmd < command_to_string.length) && (cmd >= 0))
return command_to_string[cmd];
else
return "[" + cmd + "]";
}
/**
* used for debugging
*/
String print() {
StringBuffer sb = new StringBuffer();
// main header
sb.append("Relay cell for circuit " + this.circID + "/stream "
+ streamID + " with command " + relayCommand() + ".\n");
// is the cell not recognized?
if (Encoding.byteArrayToInt(payload, 1, 2) != 0) {
sb.append(" Recognized "
+ Encoding.toHexString(payload, 100, 1, 2) + "\n");
sb.append(" DigestID " + Encoding.toHexString(digest) + "\n");
}
;
// display connection
if (isTypeBegin()) {
byte[] host = new byte[length - 1];
System.arraycopy(data, 0, host, 0, length - 1);
sb.append(" Connecting to: " + new String(host) + "\n");
} else
// display reason for end, if given
if (isTypeEnd()) {
sb.append(" End reason: " + reasonForClosing() + "\n");
} else
// display connection
if (isTypeConnected()) {
byte[] ip = new byte[length];
System.arraycopy(data, 0, ip, 0, length);
try {
sb.append(" Connected to: "
+ InetAddress.getByAddress(ip).toString() + "\n");
} catch (UnknownHostException e) {
}
} else
// display data field, if there is data AND data is not encrypted
if ((length > 0) && (relay_command != 6) && (relay_command != 7))
sb.append(" Data (" + length + " bytes)\n"
+ Encoding.toHexString(data, 100, 0, length) + "\n");
return sb.toString();
}
// DEBUG_END
}