package com.subgraph.orchid.directory.router; import com.subgraph.orchid.RouterDescriptor; import com.subgraph.orchid.TorParsingException; import com.subgraph.orchid.crypto.TorSignature; import com.subgraph.orchid.data.BandwidthHistory; import com.subgraph.orchid.data.Timestamp; import com.subgraph.orchid.directory.parsing.BasicDocumentParsingResult; import com.subgraph.orchid.directory.parsing.DocumentFieldParser; import com.subgraph.orchid.directory.parsing.DocumentParser; import com.subgraph.orchid.directory.parsing.DocumentParsingHandler; import com.subgraph.orchid.directory.parsing.DocumentParsingResult; import com.subgraph.orchid.directory.parsing.DocumentParsingResultHandler; public class RouterDescriptorParser implements DocumentParser<RouterDescriptor> { private final DocumentFieldParser fieldParser; private final boolean verifySignatures; private RouterDescriptorImpl currentDescriptor; private DocumentParsingResultHandler<RouterDescriptor> resultHandler; public RouterDescriptorParser(DocumentFieldParser fieldParser, boolean verifySignatures) { this.fieldParser = fieldParser; this.fieldParser.setHandler(createParsingHandler()); this.fieldParser.setRecognizeOpt(); this.verifySignatures = verifySignatures; } private DocumentParsingHandler createParsingHandler() { return new DocumentParsingHandler() { public void endOfDocument() { } public void parseKeywordLine() { processKeywordLine(); } }; } private void processKeywordLine() { final RouterDescriptorKeyword keyword = RouterDescriptorKeyword.findKeyword(fieldParser.getCurrentKeyword()); /* * dirspec.txt (1.2) * When interpreting a Document, software MUST ignore any KeywordLine that * starts with a keyword it doesn't recognize; */ if(!keyword.equals(RouterDescriptorKeyword.UNKNOWN_KEYWORD)) processKeyword(keyword); } private void startNewDescriptor() { fieldParser.resetRawDocument(); fieldParser.startSignedEntity(); currentDescriptor = new RouterDescriptorImpl(); } public boolean parse(DocumentParsingResultHandler<RouterDescriptor> resultHandler) { this.resultHandler = resultHandler; startNewDescriptor(); try { fieldParser.processDocument(); return true; } catch(TorParsingException e) { resultHandler.parsingError(e.getMessage()); return false; } } public DocumentParsingResult<RouterDescriptor> parse() { final BasicDocumentParsingResult<RouterDescriptor> result = new BasicDocumentParsingResult<RouterDescriptor>(); parse(result); return result; } private void processKeyword(RouterDescriptorKeyword keyword) { fieldParser.verifyExpectedArgumentCount(keyword.getKeyword(), keyword.getArgumentCount()); switch(keyword) { case ROUTER: processRouter(); return; case BANDWIDTH: processBandwidth(); break; case PLATFORM: currentDescriptor.setPlatform(fieldParser.parseConcatenatedString()); break; case PUBLISHED: currentDescriptor.setPublished(fieldParser.parseTimestamp()); break; case FINGERPRINT: currentDescriptor.setFingerprint(fieldParser.parseFingerprint()); break; case HIBERNATING: currentDescriptor.setHibernating(fieldParser.parseBoolean()); break; case UPTIME: currentDescriptor.setUptime(fieldParser.parseInteger()); break; case ONION_KEY: currentDescriptor.setOnionKey(fieldParser.parsePublicKey()); break; case NTOR_ONION_KEY: currentDescriptor.setNtorOnionKey(fieldParser.parseNtorPublicKey()); break; case SIGNING_KEY: currentDescriptor.setIdentityKey(fieldParser.parsePublicKey()); break; case ROUTER_SIGNATURE: processSignature(); break; case ACCEPT: currentDescriptor.addAcceptRule(fieldParser.parseString()); break; case REJECT: currentDescriptor.addRejectRule(fieldParser.parseString()); break; case CONTACT: currentDescriptor.setContact(fieldParser.parseConcatenatedString()); break; case FAMILY: while(fieldParser.argumentsRemaining() > 0) currentDescriptor.addFamilyMember(fieldParser.parseString()); break; case EVENTDNS: if(fieldParser.parseBoolean()) currentDescriptor.setEventDNS(); break; case PROTOCOLS: processProtocols(); break; case CACHES_EXTRA_INFO: currentDescriptor.setCachesExtraInfo(); break; case HIDDEN_SERVICE_DIR: currentDescriptor.setHiddenServiceDir(); break; case ALLOW_SINGLE_HOP_EXITS: currentDescriptor.setAllowSingleHopExits(); break; case EXTRA_INFO_DIGEST: currentDescriptor.setExtraInfoDigest(fieldParser.parseHexDigest()); break; case READ_HISTORY: currentDescriptor.setReadHistory(parseHistory()); break; case WRITE_HISTORY: currentDescriptor.setWriteHistory(parseHistory()); break; default: break; } } private BandwidthHistory parseHistory() { final Timestamp ts = fieldParser.parseTimestamp(); final String nsec = fieldParser.parseString(); fieldParser.parseString(); final int interval = fieldParser.parseInteger(nsec.substring(1)); final BandwidthHistory history = new BandwidthHistory(ts, interval); if(fieldParser.argumentsRemaining() == 0) return history; final String[] samples = fieldParser.parseString().split(","); for(String s: samples) history.addSample(fieldParser.parseInteger(s)); return history; } private void processRouter() { currentDescriptor.setNickname(fieldParser.parseNickname()); currentDescriptor.setAddress(fieldParser.parseAddress()); currentDescriptor.setRouterPort(fieldParser.parsePort()); /* 2.1 SOCKSPort is deprecated and should always be 0 */ fieldParser.parsePort(); currentDescriptor.setDirectoryPort(fieldParser.parsePort()); } private boolean verifyCurrentDescriptor(TorSignature signature) { if(verifySignatures && !fieldParser.verifySignedEntity(currentDescriptor.getIdentityKey(), signature)) { resultHandler.documentInvalid(currentDescriptor, "Signature failed."); fieldParser.logWarn("Signature failed for router: " + currentDescriptor.getNickname()); return false; } currentDescriptor.setValidSignature(); if(!currentDescriptor.isValidDocument()) { resultHandler.documentInvalid(currentDescriptor, "Router data invalid"); fieldParser.logWarn("Router data invalid for router: " + currentDescriptor.getNickname()); } return currentDescriptor.isValidDocument(); } private void processBandwidth() { final int average = fieldParser.parseInteger(); final int burst = fieldParser.parseInteger(); final int observed = fieldParser.parseInteger(); currentDescriptor.setBandwidthValues(average, burst, observed); } private void processProtocols() { String kw = fieldParser.parseString(); if(!kw.equals("Link")) throw new TorParsingException("Expected 'Link' token in protocol line got: " + kw); while(true) { kw = fieldParser.parseString(); if(kw.equals("Circuit")) break; currentDescriptor.addLinkProtocolVersion(fieldParser.parseInteger(kw)); } while(fieldParser.argumentsRemaining() > 0) currentDescriptor.addCircuitProtocolVersion(fieldParser.parseInteger()); } private void processSignature() { fieldParser.endSignedEntity(); currentDescriptor.setDescriptorHash(fieldParser.getSignatureMessageDigest().getHexDigest()); final TorSignature signature = fieldParser.parseSignature(); currentDescriptor.setRawDocumentData(fieldParser.getRawDocument()); if(verifyCurrentDescriptor(signature)) resultHandler.documentParsed(currentDescriptor); startNewDescriptor(); } }