19 package org.sleuthkit.autopsy.keywordsearch;
21 import com.google.common.base.CharMatcher;
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.HashMap;
25 import java.util.HashSet;
26 import java.util.List;
29 import java.util.logging.Level;
30 import java.util.regex.Matcher;
31 import java.util.regex.Pattern;
32 import org.apache.commons.lang.StringUtils;
33 import org.apache.solr.client.solrj.SolrQuery;
34 import org.apache.solr.client.solrj.response.TermsResponse.Term;
35 import org.openide.util.Exceptions;
45 import org.
sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
47 import org.
sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
58 final class TermsComponentQuery
implements KeywordSearchQuery {
60 private static final Logger LOGGER = Logger.getLogger(TermsComponentQuery.class.getName());
61 private static final String MODULE_NAME = KeywordSearchModuleFactory.getModuleName();
62 private static final String SEARCH_HANDLER =
"/terms";
63 private static final String SEARCH_FIELD = Server.Schema.TEXT.toString();
64 private static final int TERMS_SEARCH_TIMEOUT = 90 * 1000;
65 private static final String CASE_INSENSITIVE =
"case_insensitive";
66 private static final boolean DEBUG_FLAG = Version.Type.DEVELOPMENT.equals(Version.getBuildType());
67 private static final int MAX_TERMS_QUERY_RESULTS = 20000;
69 private final KeywordList keywordList;
70 private final Keyword originalKeyword;
71 private final List<KeywordQueryFilter> filters =
new ArrayList<>();
73 private String searchTerm;
74 private boolean searchTermIsEscaped;
86 static final Pattern CREDIT_CARD_NUM_PATTERN
87 = Pattern.compile(
"(?<ccn>[2-6]([ -]?[0-9]){11,18})");
88 static final Pattern CREDIT_CARD_TRACK1_PATTERN = Pattern.compile(
100 +
"(?<accountNumber>[2-6]([ -]?[0-9]){11,18})"
102 +
"(?<name>[^^]{2,26})"
104 +
"(?:(?:\\^|(?<expiration>\\d{4}))"
105 +
"(?:(?:\\^|(?<serviceCode>\\d{3}))"
106 +
"(?:(?<discretionary>[^?]*)"
110 static final Pattern CREDIT_CARD_TRACK2_PATTERN = Pattern.compile(
121 +
"(?<accountNumber>[2-6]([ -]?[0-9]){11,18})"
123 +
"(?:(?<expiration>\\d{4})"
124 +
"(?:(?<serviceCode>\\d{3})"
125 +
"(?:(?<discretionary>[^:;<=>?]*)"
129 static final BlackboardAttribute.Type KEYWORD_SEARCH_DOCUMENT_ID =
new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_DOCUMENT_ID);
147 TermsComponentQuery(KeywordList keywordList, Keyword keyword) {
148 this.keywordList = keywordList;
149 this.originalKeyword = keyword;
150 this.searchTerm = keyword.getSearchTerm();
160 public KeywordList getKeywordList() {
171 public String getQueryString() {
172 return originalKeyword.getSearchTerm();
183 public boolean isLiteral() {
192 public void setSubstringQuery() {
193 searchTerm =
".*" + searchTerm +
".*";
200 public void escape() {
201 searchTerm = Pattern.quote(originalKeyword.getSearchTerm());
202 searchTermIsEscaped =
true;
211 public boolean isEscaped() {
212 return searchTermIsEscaped;
222 public String getEscapedQueryString() {
223 return this.searchTerm;
232 public boolean validate() {
233 if (searchTerm.isEmpty()) {
237 Pattern.compile(searchTerm);
239 }
catch (IllegalArgumentException ex) {
251 public void setField(String field) {
261 public void addFilter(KeywordQueryFilter filter) {
262 this.filters.add(filter);
276 public QueryResults performQuery() throws KeywordSearchModuleException, NoOpenCoreException {
281 final SolrQuery termsQuery =
new SolrQuery();
282 termsQuery.setRequestHandler(SEARCH_HANDLER);
283 termsQuery.setTerms(
true);
284 termsQuery.setTermsRegexFlag(CASE_INSENSITIVE);
285 termsQuery.setTermsRegex(searchTerm);
286 termsQuery.addTermsField(SEARCH_FIELD);
287 termsQuery.setTimeAllowed(TERMS_SEARCH_TIMEOUT);
288 termsQuery.setShowDebugInfo(DEBUG_FLAG);
289 termsQuery.setTermsLimit(MAX_TERMS_QUERY_RESULTS);
290 List<Term> terms = KeywordSearch.getServer().queryTerms(termsQuery).getTerms(SEARCH_FIELD);
294 QueryResults results =
new QueryResults(
this);
295 for (Term term : terms) {
300 if (originalKeyword.getArtifactAttributeType() == ATTRIBUTE_TYPE.TSK_CARD_NUMBER) {
301 Matcher matcher = CREDIT_CARD_NUM_PATTERN.matcher(term.getTerm());
302 if (
false == matcher.find()
303 ||
false == CreditCardValidator.isValidCCN(matcher.group(
"ccn"))) {
318 String escapedTerm = KeywordSearchUtil.escapeLuceneQuery(term.getTerm());
319 LuceneQuery termQuery =
new LuceneQuery(keywordList,
new Keyword(escapedTerm,
true,
true));
320 filters.forEach(termQuery::addFilter);
321 QueryResults termQueryResult = termQuery.performQuery();
322 Set<KeywordHit> termHits =
new HashSet<>();
323 for (Keyword word : termQueryResult.getKeywords()) {
324 termHits.addAll(termQueryResult.getResults(word));
326 results.addResult(
new Keyword(term.getTerm(),
false,
true, originalKeyword.getListName(), originalKeyword.getOriginalTerm()),
new ArrayList<>(termHits));
348 public BlackboardArtifact postKeywordHitToBlackboard(Content content, Keyword foundKeyword, KeywordHit hit, String snippet, String listName) {
353 if (originalKeyword.getArtifactAttributeType() == ATTRIBUTE_TYPE.TSK_CARD_NUMBER) {
354 createCCNAccount(content, hit, snippet, listName);
362 BlackboardArtifact newArtifact;
363 Collection<BlackboardAttribute> attributes =
new ArrayList<>();
365 attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD, MODULE_NAME, foundKeyword.getSearchTerm()));
366 attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP, MODULE_NAME, originalKeyword.getSearchTerm()));
369 newArtifact = content.newArtifact(ARTIFACT_TYPE.TSK_KEYWORD_HIT);
371 }
catch (TskCoreException ex) {
372 LOGGER.log(Level.SEVERE,
"Error adding artifact for keyword hit to blackboard", ex);
376 if (StringUtils.isNotBlank(listName)) {
377 attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_SET_NAME, MODULE_NAME, listName));
379 if (snippet != null) {
380 attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_PREVIEW, MODULE_NAME, snippet));
383 hit.getArtifactID().ifPresent(
384 artifactID -> attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT, MODULE_NAME, artifactID))
388 attributes.add(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_TYPE, MODULE_NAME, KeywordSearch.QueryType.SUBSTRING.ordinal()));
391 newArtifact.addAttributes(attributes);
393 }
catch (TskCoreException e) {
394 LOGGER.log(Level.SEVERE,
"Error adding bb attributes for terms search artifact", e);
399 private void createCCNAccount(Content content, KeywordHit hit, String snippet, String listName) {
401 if (originalKeyword.getArtifactAttributeType() != ATTRIBUTE_TYPE.TSK_CARD_NUMBER) {
402 LOGGER.log(Level.SEVERE,
"Keyword hit is not a credit card number");
411 Collection<BlackboardAttribute> attributes =
new ArrayList<>();
413 Map<BlackboardAttribute.Type, BlackboardAttribute> parsedTrackAttributeMap =
new HashMap<>();
414 Matcher matcher = CREDIT_CARD_TRACK1_PATTERN.matcher(hit.getSnippet());
415 if (matcher.find()) {
416 parseTrack1Data(parsedTrackAttributeMap, matcher);
418 matcher = CREDIT_CARD_TRACK2_PATTERN.matcher(hit.getSnippet());
419 if (matcher.find()) {
420 parseTrack2Data(parsedTrackAttributeMap, matcher);
422 final BlackboardAttribute ccnAttribute = parsedTrackAttributeMap.get(
new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_CARD_NUMBER));
423 if (ccnAttribute == null || StringUtils.isBlank(ccnAttribute.getValueString())) {
424 if (hit.isArtifactHit()) {
425 LOGGER.log(Level.SEVERE, String.format(
"Failed to parse credit card account number for artifact keyword hit: term = %s, snippet = '%s', artifact id = %d", searchTerm, hit.getSnippet(), hit.getArtifactID().get()));
429 contentId = hit.getContentID();
430 }
catch (TskCoreException ex) {
431 LOGGER.log(Level.SEVERE, String.format(
"Failed to content id from keyword hit: term = %s, snippet = '%s'", searchTerm, hit.getSnippet()), ex);
434 LOGGER.log(Level.SEVERE, String.format(
"Failed to parse credit card account number for content keyword hit: term = %s, snippet = '%s', object id = %d", searchTerm, hit.getSnippet(), contentId));
436 LOGGER.log(Level.SEVERE, String.format(
"Failed to parse credit card account number for content keyword hit: term = %s, snippet = '%s'", searchTerm, hit.getSnippet()));
441 attributes.addAll(parsedTrackAttributeMap.values());
447 final int bin = Integer.parseInt(ccnAttribute.getValueString().substring(0, 8));
448 CreditCards.BankIdentificationNumber binInfo = CreditCards.getBINInfo(bin);
449 if (binInfo != null) {
450 binInfo.getScheme().ifPresent(scheme
451 -> attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_CARD_SCHEME, MODULE_NAME, scheme)));
452 binInfo.getCardType().ifPresent(cardType
453 -> attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_CARD_TYPE, MODULE_NAME, cardType)));
454 binInfo.getBrand().ifPresent(brand
455 -> attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_BRAND_NAME, MODULE_NAME, brand)));
456 binInfo.getBankName().ifPresent(bankName
457 -> attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_BANK_NAME, MODULE_NAME, bankName)));
458 binInfo.getBankPhoneNumber().ifPresent(phoneNumber
459 -> attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER, MODULE_NAME, phoneNumber)));
460 binInfo.getBankURL().ifPresent(url
461 -> attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL, MODULE_NAME, url)));
462 binInfo.getCountry().ifPresent(country
463 -> attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_COUNTRY, MODULE_NAME, country)));
464 binInfo.getBankCity().ifPresent(city
465 -> attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_CITY, MODULE_NAME, city)));
472 if (content instanceof AbstractFile) {
473 AbstractFile file = (AbstractFile) content;
474 if (file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS
475 || file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) {
476 attributes.add(
new BlackboardAttribute(KEYWORD_SEARCH_DOCUMENT_ID, MODULE_NAME, hit.getSolrDocumentId()));
480 if (StringUtils.isNotBlank(listName)) {
481 attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_SET_NAME, MODULE_NAME, listName));
483 if (snippet != null) {
484 attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_PREVIEW, MODULE_NAME, snippet));
487 hit.getArtifactID().ifPresent(
488 artifactID -> attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT, MODULE_NAME, artifactID))
492 attributes.add(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_TYPE, MODULE_NAME, KeywordSearch.QueryType.SUBSTRING.ordinal()));
498 AccountFileInstance ccAccountInstance = Case.getCurrentCaseThrows().getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.CREDIT_CARD, ccnAttribute.getValueString(), MODULE_NAME, content);
499 ccAccountInstance.addAttributes(attributes);
500 }
catch (TskCoreException | NoCurrentCaseException ex) {
501 LOGGER.log(Level.SEVERE,
"Error creating CCN account instance", ex);
514 static private void parseTrack2Data(Map<BlackboardAttribute.Type, BlackboardAttribute> attributesMap, Matcher matcher) {
515 addAttributeIfNotAlreadyCaptured(attributesMap, ATTRIBUTE_TYPE.TSK_CARD_NUMBER,
"accountNumber", matcher);
516 addAttributeIfNotAlreadyCaptured(attributesMap, ATTRIBUTE_TYPE.TSK_CARD_EXPIRATION,
"expiration", matcher);
517 addAttributeIfNotAlreadyCaptured(attributesMap, ATTRIBUTE_TYPE.TSK_CARD_SERVICE_CODE,
"serviceCode", matcher);
518 addAttributeIfNotAlreadyCaptured(attributesMap, ATTRIBUTE_TYPE.TSK_CARD_DISCRETIONARY,
"discretionary", matcher);
519 addAttributeIfNotAlreadyCaptured(attributesMap, ATTRIBUTE_TYPE.TSK_CARD_LRC,
"LRC", matcher);
531 static private void parseTrack1Data(Map<BlackboardAttribute.Type, BlackboardAttribute> attributeMap, Matcher matcher) {
532 parseTrack2Data(attributeMap, matcher);
533 addAttributeIfNotAlreadyCaptured(attributeMap, ATTRIBUTE_TYPE.TSK_NAME_PERSON,
"name", matcher);
547 static private void addAttributeIfNotAlreadyCaptured(Map<BlackboardAttribute.Type, BlackboardAttribute> attributeMap, ATTRIBUTE_TYPE attrType, String groupName, Matcher matcher) {
548 BlackboardAttribute.Type type =
new BlackboardAttribute.Type(attrType);
549 attributeMap.computeIfAbsent(type, (BlackboardAttribute.Type t) -> {
550 String value = matcher.group(groupName);
551 if (attrType.equals(ATTRIBUTE_TYPE.TSK_CARD_NUMBER)) {
552 value = CharMatcher.anyOf(
" -").removeFrom(value);
554 if (StringUtils.isNotBlank(value)) {
555 return new BlackboardAttribute(attrType, MODULE_NAME, value);