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.commons.validator.routines.checkdigit.LuhnCheckDigit;
34 import org.apache.solr.client.solrj.SolrQuery;
35 import org.apache.solr.client.solrj.response.TermsResponse.Term;
42 import org.
sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
44 import org.
sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
54 final class TermsComponentQuery
implements KeywordSearchQuery {
56 private static final Logger LOGGER = Logger.getLogger(TermsComponentQuery.class.getName());
57 private static final String MODULE_NAME = KeywordSearchModuleFactory.getModuleName();
58 private static final String SEARCH_HANDLER =
"/terms";
59 private static final String SEARCH_FIELD = Server.Schema.CONTENT_WS.toString();
60 private static final int TERMS_SEARCH_TIMEOUT = 90 * 1000;
61 private static final String CASE_INSENSITIVE =
"case_insensitive";
62 private static final boolean DEBUG_FLAG = Version.Type.DEVELOPMENT.equals(Version.getBuildType());
63 private static final int MAX_TERMS_QUERY_RESULTS = 20000;
64 private final KeywordList keywordList;
65 private final Keyword keyword;
66 private String searchTerm;
67 private boolean searchTermIsEscaped;
68 private final List<KeywordQueryFilter> filters =
new ArrayList<>();
75 private static final Pattern CREDIT_CARD_NUM_PATTERN = Pattern.compile(
"(?<ccn>[3456]([ -]?\\d){11,18})");
76 private static final LuhnCheckDigit CREDIT_CARD_NUM_LUHN_CHECK =
new LuhnCheckDigit();
77 private static final Pattern CREDIT_CARD_TRACK1_PATTERN = Pattern.compile(
89 +
"(?<accountNumber>[3456]([ -]?\\d){11,18})"
91 +
"(?<name>[^^]{2,26})"
93 +
"(?:(?:\\^|(?<expiration>\\d{4}))"
94 +
"(?:(?:\\^|(?<serviceCode>\\d{3}))"
95 +
"(?:(?<discretionary>[^?]*)"
99 private static final Pattern CREDIT_CARD_TRACK2_PATTERN = Pattern.compile(
110 +
"(?<accountNumber>[3456]([ -]?\\d){11,18})"
112 +
"(?:(?<expiration>\\d{4})"
113 +
"(?:(?<serviceCode>\\d{3})"
114 +
"(?:(?<discretionary>[^:;<=>?]*)"
118 private static final BlackboardAttribute.Type KEYWORD_SEARCH_DOCUMENT_ID =
new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_DOCUMENT_ID);
136 TermsComponentQuery(KeywordList keywordList, Keyword keyword) {
137 this.keywordList = keywordList;
138 this.keyword = keyword;
139 this.searchTerm = keyword.getSearchTerm();
149 public KeywordList getKeywordList() {
160 public String getQueryString() {
161 return keyword.getSearchTerm();
172 public boolean isLiteral() {
181 public void setSubstringQuery() {
182 searchTerm =
".*" + searchTerm +
".*";
189 public void escape() {
190 searchTerm = Pattern.quote(keyword.getSearchTerm());
191 searchTermIsEscaped =
true;
200 public boolean isEscaped() {
201 return searchTermIsEscaped;
211 public String getEscapedQueryString() {
212 return this.searchTerm;
221 public boolean validate() {
222 if (searchTerm.isEmpty()) {
226 Pattern.compile(searchTerm);
228 }
catch (IllegalArgumentException ex) {
240 public void setField(String field) {
250 public void addFilter(KeywordQueryFilter filter) {
251 this.filters.add(filter);
267 public QueryResults performQuery() throws NoOpenCoreException {
272 final SolrQuery termsQuery =
new SolrQuery();
273 termsQuery.setRequestHandler(SEARCH_HANDLER);
274 termsQuery.setTerms(
true);
275 termsQuery.setTermsRegexFlag(CASE_INSENSITIVE);
276 termsQuery.setTermsRegex(searchTerm);
277 termsQuery.addTermsField(SEARCH_FIELD);
278 termsQuery.setTimeAllowed(TERMS_SEARCH_TIMEOUT);
279 termsQuery.setShowDebugInfo(DEBUG_FLAG);
280 termsQuery.setTermsLimit(MAX_TERMS_QUERY_RESULTS);
281 List<Term> terms = null;
283 terms = KeywordSearch.getServer().queryTerms(termsQuery).getTerms(SEARCH_FIELD);
284 }
catch (KeywordSearchModuleException ex) {
285 LOGGER.log(Level.SEVERE,
"Error executing the regex terms query: " + keyword.getSearchTerm(), ex);
292 QueryResults results =
new QueryResults(
this, keywordList);
293 for (Term term : terms) {
298 if (keyword.getArtifactAttributeType() == ATTRIBUTE_TYPE.TSK_CARD_NUMBER) {
299 Matcher matcher = CREDIT_CARD_NUM_PATTERN.matcher(term.getTerm());
301 final String ccn = CharMatcher.anyOf(
" -").removeFrom(matcher.group(
"ccn"));
302 if (
false == CREDIT_CARD_NUM_LUHN_CHECK.isValid(ccn)) {
317 String escapedTerm = KeywordSearchUtil.escapeLuceneQuery(term.getTerm());
318 LuceneQuery termQuery =
new LuceneQuery(keywordList,
new Keyword(escapedTerm,
true));
319 filters.forEach(termQuery::addFilter);
320 QueryResults termQueryResult = termQuery.performQuery();
321 Set<KeywordHit> termHits =
new HashSet<>();
322 for (Keyword word : termQueryResult.getKeywords()) {
323 termHits.addAll(termQueryResult.getResults(word));
325 results.addResult(
new Keyword(term.getTerm(),
false),
new ArrayList<>(termHits));
347 public KeywordCachedArtifact writeSingleFileHitsToBlackBoard(String searchTerm, KeywordHit hit, String snippet, String listName) {
354 BlackboardArtifact newArtifact;
355 Collection<BlackboardAttribute> attributes =
new ArrayList<>();
356 if (keyword.getArtifactAttributeType() != ATTRIBUTE_TYPE.TSK_CARD_NUMBER) {
357 attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD, MODULE_NAME, searchTerm));
358 attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP, MODULE_NAME, keyword.getSearchTerm()));
360 newArtifact = hit.getContent().newArtifact(ARTIFACT_TYPE.TSK_KEYWORD_HIT);
362 }
catch (TskCoreException ex) {
363 LOGGER.log(Level.SEVERE,
"Error adding artifact for keyword hit to blackboard", ex);
371 attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE, MODULE_NAME, Account.Type.CREDIT_CARD.name()));
372 Map<BlackboardAttribute.Type, BlackboardAttribute> parsedTrackAttributeMap =
new HashMap<>();
373 Matcher matcher = CREDIT_CARD_TRACK1_PATTERN.matcher(hit.getSnippet());
374 if (matcher.find()) {
375 parseTrack1Data(parsedTrackAttributeMap, matcher);
377 matcher = CREDIT_CARD_TRACK2_PATTERN.matcher(hit.getSnippet());
378 if (matcher.find()) {
379 parseTrack2Data(parsedTrackAttributeMap, matcher);
381 final BlackboardAttribute ccnAttribute = parsedTrackAttributeMap.get(
new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_CARD_NUMBER));
382 if (ccnAttribute == null || StringUtils.isBlank(ccnAttribute.getValueString())) {
383 if (hit.isArtifactHit()) {
384 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.getArtifact().getArtifactID()));
386 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(), hit.getContent().getId()));
390 attributes.addAll(parsedTrackAttributeMap.values());
396 final int bin = Integer.parseInt(ccnAttribute.getValueString().substring(0, 8));
397 CreditCards.BankIdentificationNumber binInfo = CreditCards.getBINInfo(bin);
398 if (binInfo != null) {
399 binInfo.getScheme().ifPresent(scheme
400 -> attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_CARD_SCHEME, MODULE_NAME, scheme)));
401 binInfo.getCardType().ifPresent(cardType
402 -> attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_CARD_TYPE, MODULE_NAME, cardType)));
403 binInfo.getBrand().ifPresent(brand
404 -> attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_BRAND_NAME, MODULE_NAME, brand)));
405 binInfo.getBankName().ifPresent(bankName
406 -> attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_BANK_NAME, MODULE_NAME, bankName)));
407 binInfo.getBankPhoneNumber().ifPresent(phoneNumber
408 -> attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER, MODULE_NAME, phoneNumber)));
409 binInfo.getBankURL().ifPresent(url
410 -> attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL, MODULE_NAME, url)));
411 binInfo.getCountry().ifPresent(country
412 -> attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_COUNTRY, MODULE_NAME, country)));
413 binInfo.getBankCity().ifPresent(city
414 -> attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_CITY, MODULE_NAME, city)));
422 if (hit.getContent() instanceof AbstractFile) {
423 AbstractFile file = (AbstractFile) hit.getContent();
424 if (file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS
425 || file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) {
426 attributes.add(
new BlackboardAttribute(KEYWORD_SEARCH_DOCUMENT_ID, MODULE_NAME, hit.getSolrDocumentId()));
434 newArtifact = hit.getContent().newArtifact(ARTIFACT_TYPE.TSK_ACCOUNT);
435 }
catch (TskCoreException ex) {
436 LOGGER.log(Level.SEVERE,
"Error adding artifact for account to blackboard", ex);
441 if (StringUtils.isNotBlank(listName)) {
442 attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_SET_NAME, MODULE_NAME, listName));
444 if (snippet != null) {
445 attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_PREVIEW, MODULE_NAME, snippet));
447 if (hit.isArtifactHit()) {
448 attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT, MODULE_NAME, hit.getArtifact().getArtifactID()));
452 newArtifact.addAttributes(attributes);
453 KeywordCachedArtifact writeResult =
new KeywordCachedArtifact(newArtifact);
454 writeResult.add(attributes);
456 }
catch (TskCoreException e) {
457 LOGGER.log(Level.SEVERE,
"Error adding bb attributes for terms search artifact", e);
470 static private void parseTrack2Data(Map<BlackboardAttribute.Type, BlackboardAttribute> attributesMap, Matcher matcher) {
471 addAttributeIfNotAlreadyCaptured(attributesMap, ATTRIBUTE_TYPE.TSK_CARD_NUMBER,
"accountNumber", matcher);
472 addAttributeIfNotAlreadyCaptured(attributesMap, ATTRIBUTE_TYPE.TSK_CARD_EXPIRATION,
"expiration", matcher);
473 addAttributeIfNotAlreadyCaptured(attributesMap, ATTRIBUTE_TYPE.TSK_CARD_SERVICE_CODE,
"serviceCode", matcher);
474 addAttributeIfNotAlreadyCaptured(attributesMap, ATTRIBUTE_TYPE.TSK_CARD_DISCRETIONARY,
"discretionary", matcher);
475 addAttributeIfNotAlreadyCaptured(attributesMap, ATTRIBUTE_TYPE.TSK_CARD_LRC,
"LRC", matcher);
487 static private void parseTrack1Data(Map<BlackboardAttribute.Type, BlackboardAttribute> attributeMap, Matcher matcher) {
488 parseTrack2Data(attributeMap, matcher);
489 addAttributeIfNotAlreadyCaptured(attributeMap, ATTRIBUTE_TYPE.TSK_NAME_PERSON,
"name", matcher);
503 static private void addAttributeIfNotAlreadyCaptured(Map<BlackboardAttribute.Type, BlackboardAttribute> attributeMap, ATTRIBUTE_TYPE attrType, String groupName, Matcher matcher) {
504 BlackboardAttribute.Type type =
new BlackboardAttribute.Type(attrType);
505 attributeMap.computeIfAbsent(type, (BlackboardAttribute.Type t) -> {
506 String value = matcher.group(groupName);
507 if (attrType.equals(ATTRIBUTE_TYPE.TSK_CARD_NUMBER)) {
508 value = CharMatcher.anyOf(
" -").removeFrom(value);
510 if (StringUtils.isNotBlank(value)) {
511 return new BlackboardAttribute(attrType, MODULE_NAME, value);