package com.netifera.platform.net.daemon.sniffing.modules.passivefp;
import java.nio.CharBuffer;
import com.netifera.platform.net.packets.tcpip.TCP;
final class Signature {
final public static int QUIRK_PAST = 0x0001;
final public static int QUIRK_ZEROID = 0x0002;
final public static int QUIRK_IPOPT = 0x0004;
final public static int QUIRK_URG = 0x0008;
final public static int QUIRK_X2 = 0x0010;
final public static int QUIRK_ACK = 0x0020;
final public static int QUIRK_T2 = 0x0040;
final public static int QUIRK_FLAGS = 0x0080;
final public static int QUIRK_DATA = 0x0100;
final public static int QUIRK_BROKEN = 0x0200;
final public static int QUIRK_RSTACK = 0x0400;
final public static int QUIRK_SEQEQ = 0x0800;
final public static int QUIRK_SEQ0 = 0x1000;
public final static int MAXOPT = 16;
final private static int IDX_WINSIZE = 0;
final private static int IDX_TTL = 1;
final private static int IDX_DF = 2;
final private static int IDX_SIZE = 3;
final private static int IDX_OPTIONS = 4;
final private static int IDX_QUIRKS = 5;
final private static int IDX_GENRE = 6;
final private static int IDX_DESC = 7;
final private static int PACKET_BIG = 100;
enum WindowMod { MOD_NONE, MOD_CONST, MOD_MSS, MOD_MTU };
enum SignatureType { TYPE_SYN, TYPE_ACK, TYPE_RST, TYPE_OPEN };
private final SignatureType type;
private String OSGenre;
private String OSVersion;
private String OSNotes;
@SuppressWarnings("unused")
private boolean generic;
@SuppressWarnings("unused")
private boolean userland;
@SuppressWarnings("unused")
private boolean noDetail;
private int windowSize;
private WindowMod windowSizeMod;
private int ttl;
private int packetSize;
private boolean dfFlag;
private int wsc;
private WindowMod wscMod;
private int mss;
private WindowMod mssMod;
private boolean zeroStamp;
private byte[] optionValues = new byte[MAXOPT];
private int optionCount = 0;
private int quirks;
static Signature createSynSignature(String signatureString) throws ParseException {
Signature s = new Signature(SignatureType.TYPE_SYN);
s.parse(signatureString);
return s;
}
static Signature createAckSignature(String signatureString) throws ParseException {
Signature s = new Signature(SignatureType.TYPE_ACK);
s.parse(signatureString);
return s;
}
static Signature createRstSignature(String signatureString) throws ParseException {
Signature s = new Signature(SignatureType.TYPE_RST);
s.parse(signatureString);
return s;
}
static Signature createOpenSignature(String signatureString) throws ParseException {
Signature s = new Signature(SignatureType.TYPE_OPEN);
s.parse(signatureString);
return s;
}
private Signature(SignatureType type) {
this.type = type;
}
String getOSGenre() {
return OSGenre;
}
String getOSVersion() {
return OSVersion;
}
String getOSNotes() {
return OSNotes;
}
boolean match(int len, boolean df, int ttl, int wss, byte[] ops, int nops,
int mss, int wsc, int tstamp, int tos, int quirks) {
if(packetSize == 0 && len < PACKET_BIG) return false;
if(len != packetSize) return false;
if(nops != optionCount) return false;
if(zeroStamp) {
if(tstamp != 0) return false;
} else {
if(tstamp == 0) return false;
}
if(df != dfFlag) return false;
if(quirks != this.quirks) return false;
if(mssMod == WindowMod.MOD_NONE) {
if(mss != this.mss) return false;
} else {
if(mss % this.mss != 0) return false;
}
if(wscMod == WindowMod.MOD_NONE) {
if(wsc != this.wsc) return false;
} else {
if(wsc % this.wsc != 0) return false;
}
switch(windowSizeMod) {
case MOD_NONE:
if(wss != windowSize) return false;
break;
case MOD_CONST:
if(wss % windowSize != 0) return false;
break;
case MOD_MSS:
if(mss > 0 && (wss % mss) == 0) {
if((wss / mss) != windowSize) return false;
} else if(wss % 1460 == 0) {
if(wss / 1460 != windowSize) return false;
} else {
return false;
}
break;
case MOD_MTU:
if(mss > 0 && (wss % (mss + 40)) == 0) {
if(wss / (mss + 40) != windowSize) return false;
} else if(wss % 1500 == 0) {
if(wss / 1500 != windowSize) return false;
} else {
return false;
}
break;
}
for(int i = 0; i < nops; i++) {
if(optionValues[i] != ops[i]) return false;
}
if(this.ttl < ttl) {
// if(use_fuzzy) fuzzy = p;
return false;
}
return true;
}
private void parseGenre(String genre) throws ParseException {
String os = genre;
while(true) {
char c = os.charAt(0);
switch(c) {
case '-':
userland = true;
break;
case '*':
noDetail = true;
break;
case '@':
generic = true;
break;
default:
OSGenre = os;
return;
}
os = os.substring(1);
if(os.length() == 0) {
throw new ParseException("Empty genre field");
}
}
}
private void parseDescription(String description) {
String[] parts = description.split(" ", 2);
if(parts.length > 0 && parts[0].length() > 0) {
OSVersion = parts[0];
}
if(parts.length > 1 && parts[1].length() > 0) {
OSNotes = parts[1];
}
}
private void parseWindowSize(String wsize) throws ParseException {
if(wsize.length() == 0) {
throw new ParseException("Empty window size");
}
try {
switch(wsize.charAt(0)) {
case '*':
windowSizeMod = WindowMod.MOD_CONST;
windowSize = 1;
break;
case 'S':
windowSizeMod = WindowMod.MOD_MSS;
windowSize = Integer.parseInt(wsize.substring(1));
break;
case 'T':
windowSizeMod = WindowMod.MOD_MTU;
windowSize = Integer.parseInt(wsize.substring(1));
break;
case '%':
windowSizeMod = WindowMod.MOD_CONST;
windowSize = Integer.parseInt(wsize.substring(1));
break;
default:
windowSizeMod = WindowMod.MOD_NONE;
windowSize = Integer.parseInt(wsize);
break;
}
} catch(NumberFormatException e) {
throw new ParseException("Error parsing integer in window size");
}
}
private void addOption(int option) throws ParseException {
if(optionCount >= MAXOPT) {
throw new ParseException("Too many options");
}
optionValues[optionCount++] = (byte) option;
}
private void parseOptions(CharBuffer optionsBuffer) throws ParseException {
zeroStamp = true;
wscMod = WindowMod.MOD_NONE;
mssMod = WindowMod.MOD_NONE;
if(optionsBuffer.charAt(0) == '.') {
optionsBuffer.get();
}
while(optionsBuffer.hasRemaining()) {
switch(optionsBuffer.get()) {
case 'N':
addOption(TCP.OPT_NOP);
break;
case 'E':
addOption(TCP.OPT_EOL);
if(optionsBuffer.hasRemaining()) {
throw new ParseException("EOL is not the last option");
}
break;
case 'S':
addOption(TCP.OPT_SACKOK);
break;
case 'T':
addOption(TCP.OPT_TIMESTAMP);
if(optionsBuffer.hasRemaining() && optionsBuffer.charAt(0) != '0') {
zeroStamp = false;
}
break;
case 'W':
addOption(TCP.OPT_WSCALE);
parseWScale(optionsBuffer);
break;
case 'M':
addOption(TCP.OPT_MAXSEG);
parseMaxSeg(optionsBuffer);
break;
case '?':
addOption(atoi(optionsBuffer));
break;
default:
throw new ParseException("Unknown option value");
}
// Skip characters that are not letters or '?'
while(optionsBuffer.hasRemaining() && !Character.isLetter(optionsBuffer.charAt(0)) && optionsBuffer.charAt(0) != '?') {
optionsBuffer.get();
}
}
}
private void parseQuirks(CharBuffer quirkBuffer) throws ParseException {
while(quirkBuffer.hasRemaining()) {
switch(quirkBuffer.get()) {
case 'K':
if(type != SignatureType.TYPE_RST) {
throw new ParseException("Quirk 'K' is only valid in RST mode");
}
quirks |= QUIRK_RSTACK;
break;
case 'D':
if(type == SignatureType.TYPE_OPEN) {
throw new ParseException("Quirk 'D' is not valid in open mode");
}
quirks |= QUIRK_DATA;
break;
case 'Q':
quirks |= QUIRK_SEQEQ;
break;
case '0':
quirks |= QUIRK_SEQ0;
break;
case 'P':
quirks |= QUIRK_PAST;
break;
case 'Z':
quirks |= QUIRK_ZEROID;
break;
case 'I':
quirks |= QUIRK_IPOPT;
break;
case 'U':
quirks |= QUIRK_URG;
break;
case 'X':
quirks |= QUIRK_X2;
break;
case 'A':
quirks |= QUIRK_ACK;
break;
case 'T':
quirks |= QUIRK_T2;
break;
case 'F':
quirks |= QUIRK_FLAGS;
break;
case '!':
quirks |= QUIRK_BROKEN;
break;
case '.':
break;
default:
throw new ParseException("Bad quirk type");
}
}
}
private int atoi(CharBuffer buffer) {
int val = 0;
while( buffer.hasRemaining() && Character.isDigit( buffer.charAt(0) )) {
val *= 10;
val += Character.digit(buffer.get(), 10);
}
return val;
}
private void parseWScale(CharBuffer wscaleBuffer) throws ParseException {
if(!wscaleBuffer.hasRemaining()) {
throw new ParseException("Empty window scale field");
}
char c = wscaleBuffer.charAt(0);
if(c == '*') {
wscaleBuffer.get();
wsc = 1;
wscMod = WindowMod.MOD_CONST;
} else if (c == '%') {
wscaleBuffer.get();
wsc = atoi(wscaleBuffer);
if(wsc == 0) {
throw new ParseException("Illegal null modulo for window scale");
}
wscMod = WindowMod.MOD_CONST;
} else {
wsc = atoi(wscaleBuffer);
wscMod = WindowMod.MOD_NONE;
}
}
private void parseMaxSeg(CharBuffer mssBuffer) throws ParseException {
if(!mssBuffer.hasRemaining()) {
throw new ParseException("Empty max segment size field");
}
char c = mssBuffer.charAt(0);
if(c == '*') {
mssBuffer.get();
mss = 1;
mssMod= WindowMod.MOD_CONST;
} else if(c == '%') {
mssBuffer.get();
mss = atoi(mssBuffer);
if(mss == 0) {
throw new ParseException("Illegal null modulo for MSS");
}
mssMod = WindowMod.MOD_CONST;
} else {
mss = atoi(mssBuffer);
wscMod = WindowMod.MOD_NONE;
}
}
private void parse(String signature) throws ParseException {
String[] parts = signature.split(":");
if(parts.length != 8) {
throw new ParseException("Too few fields (" + parts.length + ")");
}
parseGenre(parts[IDX_GENRE]);
parseDescription(parts[IDX_DESC]);
try {
ttl = Integer.parseInt(parts[IDX_TTL]);
if(parts[IDX_SIZE].charAt(0) == '*') {
packetSize = 0;
} else {
if(type == SignatureType.TYPE_OPEN) {
throw new ParseException("Packet size must be '*' in open mode signatures");
}
packetSize = Integer.parseInt(parts[IDX_SIZE]);
}
int df = Integer.parseInt(parts[IDX_DF]);
if(df != 0) {
dfFlag = true;
}
} catch(NumberFormatException e) {
throw new ParseException("Integer parsing error");
}
parseWindowSize(parts[IDX_WINSIZE]);
parseOptions(CharBuffer.wrap(parts[IDX_OPTIONS]));
parseQuirks(CharBuffer.wrap(parts[IDX_QUIRKS]));
}
}