Autopsy  4.14.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
CorrelationAttributeUtil.java
Go to the documentation of this file.
1 /*
2  * Central Repository
3  *
4  * Copyright 2017-2020 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.centralrepository.datamodel;
20 
21 import java.util.ArrayList;
22 import java.util.HashSet;
23 import java.util.List;
24 import java.util.Set;
25 import java.util.logging.Level;
26 import org.openide.util.NbBundle.Messages;
31 import org.sleuthkit.datamodel.AbstractFile;
32 import org.sleuthkit.datamodel.Account;
33 import org.sleuthkit.datamodel.BlackboardArtifact;
34 import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
35 import org.sleuthkit.datamodel.BlackboardAttribute;
36 import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
37 import org.sleuthkit.datamodel.CommunicationsUtils;
38 import org.sleuthkit.datamodel.HashUtility;
39 import org.sleuthkit.datamodel.TskCoreException;
40 import org.sleuthkit.datamodel.TskData;
41 
47 
48  private static final Logger logger = Logger.getLogger(CorrelationAttributeUtil.class.getName());
49 
60  @Messages({"CorrelationAttributeUtil.emailaddresses.text=Email Addresses"})
61  private static String getEmailAddressAttrDisplayName() {
62  return Bundle.CorrelationAttributeUtil_emailaddresses_text();
63  }
64 
65  // Defines which artifact types act as the sources for CR data.
66  // Most notably, does not include KEYWORD HIT, CALLLOGS, MESSAGES, CONTACTS
67  // TSK_INTERESTING_ARTIFACT_HIT (See JIRA-6129 for more details on the
68  // interesting artifact hit).
69 
70  // IMPORTANT: This set should be updated for new artifacts types that need to
71  // be inserted into the CR.
72  private static final Set<Integer> SOURCE_TYPES_FOR_CR_INSERT = new HashSet<Integer>() {{
73  add(ARTIFACT_TYPE.TSK_WEB_BOOKMARK.getTypeID());
74  add(ARTIFACT_TYPE.TSK_WEB_COOKIE.getTypeID());
75  add(ARTIFACT_TYPE.TSK_WEB_DOWNLOAD.getTypeID());
76  add(ARTIFACT_TYPE.TSK_WEB_HISTORY.getTypeID());
77  add(ARTIFACT_TYPE.TSK_DEVICE_ATTACHED.getTypeID());
78  add(ARTIFACT_TYPE.TSK_WIFI_NETWORK.getTypeID());
79  add(ARTIFACT_TYPE.TSK_WIFI_NETWORK_ADAPTER.getTypeID());
80  add(ARTIFACT_TYPE.TSK_BLUETOOTH_PAIRING.getTypeID());
81  add(ARTIFACT_TYPE.TSK_BLUETOOTH_ADAPTER.getTypeID());
82  add(ARTIFACT_TYPE.TSK_DEVICE_INFO.getTypeID());
83  add(ARTIFACT_TYPE.TSK_SIM_ATTACHED.getTypeID());
84  add(ARTIFACT_TYPE.TSK_WEB_FORM_ADDRESS.getTypeID());
85  add(ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID());
86  }};
87 
101  public static List<CorrelationAttributeInstance> makeCorrAttrsToSave(BlackboardArtifact artifact) {
102  if(SOURCE_TYPES_FOR_CR_INSERT.contains(artifact.getArtifactTypeID())) {
103  // Restrict the correlation attributes to use for saving.
104  // The artifacts which are suitable for saving are a subset of the
105  // artifacts that are suitable for correlating.
106  return makeCorrAttrsForCorrelation(artifact);
107  }
108  // Return an empty collection.
109  return new ArrayList<>();
110  }
111 
136  public static List<CorrelationAttributeInstance> makeCorrAttrsForCorrelation(BlackboardArtifact artifact) {
137  List<CorrelationAttributeInstance> correlationAttrs = new ArrayList<>();
138  try {
139  BlackboardArtifact sourceArtifact = getCorrAttrSourceArtifact(artifact);
140  if (sourceArtifact != null) {
141  int artifactTypeID = sourceArtifact.getArtifactTypeID();
142  if (artifactTypeID == ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()) {
143  BlackboardAttribute setNameAttr = sourceArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME));
144  if (setNameAttr != null && CorrelationAttributeUtil.getEmailAddressAttrDisplayName().equals(setNameAttr.getValueString())) {
145  makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD, CorrelationAttributeInstance.EMAIL_TYPE_ID);
146  }
147  } else if (artifactTypeID == ARTIFACT_TYPE.TSK_WEB_BOOKMARK.getTypeID()
148  || artifactTypeID == ARTIFACT_TYPE.TSK_WEB_COOKIE.getTypeID()
149  || artifactTypeID == ARTIFACT_TYPE.TSK_WEB_DOWNLOAD.getTypeID()
150  || artifactTypeID == ARTIFACT_TYPE.TSK_WEB_HISTORY.getTypeID()) {
151  makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN, CorrelationAttributeInstance.DOMAIN_TYPE_ID);
152 
153  } else if (artifactTypeID == ARTIFACT_TYPE.TSK_DEVICE_ATTACHED.getTypeID()) {
154  makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_ID, CorrelationAttributeInstance.USBID_TYPE_ID);
155  makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MAC_ADDRESS, CorrelationAttributeInstance.MAC_TYPE_ID);
156 
157  } else if (artifactTypeID == ARTIFACT_TYPE.TSK_WIFI_NETWORK.getTypeID()) {
158  makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SSID, CorrelationAttributeInstance.SSID_TYPE_ID);
159 
160  } else if (artifactTypeID == ARTIFACT_TYPE.TSK_WIFI_NETWORK_ADAPTER.getTypeID()
161  || artifactTypeID == ARTIFACT_TYPE.TSK_BLUETOOTH_PAIRING.getTypeID()
162  || artifactTypeID == ARTIFACT_TYPE.TSK_BLUETOOTH_ADAPTER.getTypeID()) {
163  makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MAC_ADDRESS, CorrelationAttributeInstance.MAC_TYPE_ID);
164 
165  } else if (artifactTypeID == ARTIFACT_TYPE.TSK_DEVICE_INFO.getTypeID()) {
166  makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_IMEI, CorrelationAttributeInstance.IMEI_TYPE_ID);
167  makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_IMSI, CorrelationAttributeInstance.IMSI_TYPE_ID);
168  makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ICCID, CorrelationAttributeInstance.ICCID_TYPE_ID);
169 
170  } else if (artifactTypeID == ARTIFACT_TYPE.TSK_SIM_ATTACHED.getTypeID()) {
171  makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_IMSI, CorrelationAttributeInstance.IMSI_TYPE_ID);
172  makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ICCID, CorrelationAttributeInstance.ICCID_TYPE_ID);
173 
174  } else if (artifactTypeID == ARTIFACT_TYPE.TSK_WEB_FORM_ADDRESS.getTypeID()) {
175  makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER, CorrelationAttributeInstance.PHONE_TYPE_ID);
176  makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL, CorrelationAttributeInstance.EMAIL_TYPE_ID);
177 
178  } else if (artifactTypeID == ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID()) {
179  makeCorrAttrFromAcctArtifact(correlationAttrs, sourceArtifact);
180 
181  } else if (artifactTypeID == ARTIFACT_TYPE.TSK_CONTACT.getTypeID()
182  || artifactTypeID == ARTIFACT_TYPE.TSK_CALLLOG.getTypeID()
183  || artifactTypeID == ARTIFACT_TYPE.TSK_MESSAGE.getTypeID()) {
184  makeCorrAttrsFromCommunicationArtifacts(correlationAttrs, sourceArtifact);
185  }
186  }
187  } catch (CentralRepoException ex) {
188  logger.log(Level.SEVERE, String.format("Error querying central repository (%s)", artifact), ex); // NON-NLS
189  return correlationAttrs;
190  } catch (TskCoreException ex) {
191  logger.log(Level.SEVERE, String.format("Error getting querying case database (%s)", artifact), ex); // NON-NLS
192  return correlationAttrs;
193  } catch (NoCurrentCaseException ex) {
194  logger.log(Level.SEVERE, "Error getting current case", ex); // NON-NLS
195  return correlationAttrs;
196  }
197  return correlationAttrs;
198  }
199 
214  private static void makeCorrAttrsFromCommunicationArtifacts(List<CorrelationAttributeInstance> corrAttrInstances, BlackboardArtifact artifact) throws TskCoreException, CentralRepoException {
215  CorrelationAttributeInstance corrAttr = null;
216 
217  /*
218  * Extract the phone number from the artifact attribute.
219  */
220  String value = null;
221  if (null != artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER))) {
222  value = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER)).getValueString();
223  } else if (null != artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM))) {
224  value = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM)).getValueString();
225  } else if (null != artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO))) {
226  value = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO)).getValueString();
227  }
228 
229  /*
230  * Normalize the phone number.
231  */
232  if (value != null) {
233  if(CommunicationsUtils.isValidPhoneNumber(value)) {
234  value = CommunicationsUtils.normalizePhoneNum(value);
236  if(corrAttr != null) {
237  corrAttrInstances.add(corrAttr);
238  }
239  }
240  }
241  }
242 
256  private static BlackboardArtifact getCorrAttrSourceArtifact(BlackboardArtifact artifact) throws NoCurrentCaseException, TskCoreException {
257  BlackboardArtifact sourceArtifact = null;
258  if (BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT.getTypeID() == artifact.getArtifactTypeID()) {
259  BlackboardAttribute assocArtifactAttr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT));
260  if (assocArtifactAttr != null) {
261  sourceArtifact = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboardArtifact(assocArtifactAttr.getValueLong());
262  }
263  } else {
264  sourceArtifact = artifact;
265  }
266  return sourceArtifact;
267  }
268 
282  private static void makeCorrAttrFromAcctArtifact(List<CorrelationAttributeInstance> corrAttrInstances, BlackboardArtifact acctArtifact) throws TskCoreException, CentralRepoException {
283 
284  // Get the account type from the artifact
285  BlackboardAttribute accountTypeAttribute = acctArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE));
286  String accountTypeStr = accountTypeAttribute.getValueString();
287 
288  // @@TODO Vik-6136: CR currently does not know of custom account types.
289  // Ensure there is a predefined account type for this account.
290  Account.Type predefinedAccountType = Account.Type.PREDEFINED_ACCOUNT_TYPES.stream().filter(type -> type.getTypeName().equalsIgnoreCase(accountTypeStr)).findAny().orElse(null);
291 
292  // do not create any correlation attribute instance for a Device account
293  if (Account.Type.DEVICE.getTypeName().equalsIgnoreCase(accountTypeStr) == false && predefinedAccountType != null) {
294 
295  // Get the corresponding CentralRepoAccountType from the database.
297 
298  int corrTypeId = crAccountType.getCorrelationTypeId();
300 
301  // Get the account identifier
302  BlackboardAttribute accountIdAttribute = acctArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ID));
303  String accountIdStr = accountIdAttribute.getValueString();
304 
305  // add/get the account and get its accountId.
306  CentralRepoAccount crAccount = CentralRepository.getInstance().getOrCreateAccount(crAccountType, accountIdStr);
307 
308  CorrelationAttributeInstance corrAttr = makeCorrAttr(acctArtifact, corrType, accountIdStr);
309  if (corrAttr != null) {
310  // set the account_id in correlation attribute
311  corrAttr.setAccountId(crAccount.getAccountId());
312  corrAttrInstances.add(corrAttr);
313  }
314  }
315  }
316 
334  private static void makeCorrAttrFromArtifactAttr(List<CorrelationAttributeInstance> corrAttrInstances, BlackboardArtifact artifact, ATTRIBUTE_TYPE artAttrType, int typeId) throws CentralRepoException, TskCoreException {
335  BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(artAttrType));
336  if (attribute != null) {
337  String value = attribute.getValueString();
338  if ((null != value) && (value.isEmpty() == false)) {
340  if (inst != null) {
341  corrAttrInstances.add(inst);
342  }
343  }
344  }
345  }
346 
364  private static CorrelationAttributeInstance makeCorrAttr(BlackboardArtifact artifact, CorrelationAttributeInstance.Type correlationType, String value) {
365  try {
366  Case currentCase = Case.getCurrentCaseThrows();
367  AbstractFile bbSourceFile = currentCase.getSleuthkitCase().getAbstractFileById(artifact.getObjectID());
368  if (null == bbSourceFile) {
369  logger.log(Level.SEVERE, "Error creating artifact instance. Abstract File was null."); // NON-NLS
370  return null;
371  }
372 
374  return new CorrelationAttributeInstance(
375  correlationType,
376  value,
377  correlationCase,
378  CorrelationDataSource.fromTSKDataSource(correlationCase, bbSourceFile.getDataSource()),
379  bbSourceFile.getParentPath() + bbSourceFile.getName(),
380  "",
381  TskData.FileKnown.UNKNOWN,
382  bbSourceFile.getId());
383 
384  } catch (TskCoreException ex) {
385  logger.log(Level.SEVERE, String.format("Error getting querying case database (%s)", artifact), ex); // NON-NLS
386  return null;
387  } catch (CentralRepoException ex) {
388  logger.log(Level.SEVERE, String.format("Error querying central repository (%s)", artifact), ex); // NON-NLS
389  return null;
391  logger.log(Level.WARNING, String.format("Error creating correlation attribute instance (%s)", artifact), ex); // NON-NLS
392  return null;
393  } catch (NoCurrentCaseException ex) {
394  logger.log(Level.SEVERE, "Error getting current case", ex); // NON-NLS
395  return null;
396  }
397  }
398 
415  public static CorrelationAttributeInstance getCorrAttrForFile(AbstractFile file) {
416 
417  if (!isSupportedAbstractFileType(file)) {
418  return null;
419  }
420 
422  CorrelationCase correlationCase;
423  CorrelationDataSource correlationDataSource;
424 
425  try {
428  if (null == correlationCase) {
429  //if the correlationCase is not in the Central repo then attributes generated in relation to it will not be
430  return null;
431  }
432  correlationDataSource = CorrelationDataSource.fromTSKDataSource(correlationCase, file.getDataSource());
433  } catch (TskCoreException ex) {
434  logger.log(Level.SEVERE, String.format("Error getting querying case database (%s)", file), ex); // NON-NLS
435  return null;
436  } catch (CentralRepoException ex) {
437  logger.log(Level.SEVERE, String.format("Error querying central repository (%s)", file), ex); // NON-NLS
438  return null;
439  } catch (NoCurrentCaseException ex) {
440  logger.log(Level.SEVERE, "Error getting current case", ex); // NON-NLS
441  return null;
442  }
443 
444  CorrelationAttributeInstance correlationAttributeInstance;
445  try {
446  correlationAttributeInstance = CentralRepository.getInstance().getCorrelationAttributeInstance(type, correlationCase, correlationDataSource, file.getId());
447  } catch (CentralRepoException ex) {
448  logger.log(Level.SEVERE, String.format("Error querying central repository (%s)", file), ex); // NON-NLS
449  return null;
451  logger.log(Level.WARNING, String.format("Error creating correlation attribute instance (%s)", file), ex); // NON-NLS
452  return null;
453  }
454 
455  /*
456  * If no correlation attribute instance was found when querying by file
457  * object ID, try searching by file path instead. This is necessary
458  * because file object IDs were not stored in the central repository in
459  * early versions of its schema.
460  */
461  if (correlationAttributeInstance == null && file.getMd5Hash() != null) {
462  String filePath = (file.getParentPath() + file.getName()).toLowerCase();
463  try {
464  correlationAttributeInstance = CentralRepository.getInstance().getCorrelationAttributeInstance(type, correlationCase, correlationDataSource, file.getMd5Hash(), filePath);
465  } catch (CentralRepoException ex) {
466  logger.log(Level.SEVERE, String.format("Error querying central repository (%s)", file), ex); // NON-NLS
467  return null;
469  logger.log(Level.WARNING, String.format("Error creating correlation attribute instance (%s)", file), ex); // NON-NLS
470  return null;
471  }
472  }
473 
474  return correlationAttributeInstance;
475  }
476 
495  public static CorrelationAttributeInstance makeCorrAttrFromFile(AbstractFile file) {
496 
497  if (!isSupportedAbstractFileType(file)) {
498  return null;
499  }
500 
501  // We need a hash to make the correlation artifact instance.
502  String md5 = file.getMd5Hash();
503  if (md5 == null || md5.isEmpty() || HashUtility.isNoDataMd5(md5)) {
504  return null;
505  }
506 
507  try {
509 
511  return new CorrelationAttributeInstance(
512  filesType,
513  file.getMd5Hash(),
514  correlationCase,
515  CorrelationDataSource.fromTSKDataSource(correlationCase, file.getDataSource()),
516  file.getParentPath() + file.getName(),
517  "",
518  TskData.FileKnown.UNKNOWN,
519  file.getId());
520 
521  } catch (TskCoreException ex) {
522  logger.log(Level.SEVERE, String.format("Error querying case database (%s)", file), ex); // NON-NLS
523  return null;
524  } catch (CentralRepoException ex) {
525  logger.log(Level.SEVERE, String.format("Error querying central repository (%s)", file), ex); // NON-NLS
526  return null;
528  logger.log(Level.WARNING, String.format("Error creating correlation attribute instance (%s)", file), ex); // NON-NLS
529  return null;
530  } catch (NoCurrentCaseException ex) {
531  logger.log(Level.SEVERE, "Error getting current case", ex); // NON-NLS
532  return null;
533  }
534  }
535 
544  public static boolean isSupportedAbstractFileType(AbstractFile file) {
545  if (file == null) {
546  return false;
547  }
548  switch (file.getType()) {
549  case UNALLOC_BLOCKS:
550  case UNUSED_BLOCKS:
551  case SLACK:
552  case VIRTUAL_DIR:
553  case LOCAL_DIR:
554  return false;
555  case CARVED:
556  case DERIVED:
557  case LOCAL:
558  case LAYOUT_FILE:
559  return true;
560  case FS:
561  return file.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC);
562  default:
563  logger.log(Level.WARNING, "Unexpected file type {0}", file.getType().getName());
564  return false;
565  }
566  }
567 
572  }
573 
574 }
static CorrelationAttributeInstance makeCorrAttr(BlackboardArtifact artifact, CorrelationAttributeInstance.Type correlationType, String value)
static CorrelationDataSource fromTSKDataSource(CorrelationCase correlationCase, Content dataSource)
static CorrelationAttributeInstance makeCorrAttrFromFile(AbstractFile file)
static List< CorrelationAttributeInstance > makeCorrAttrsForCorrelation(BlackboardArtifact artifact)
static void makeCorrAttrsFromCommunicationArtifacts(List< CorrelationAttributeInstance > corrAttrInstances, BlackboardArtifact artifact)
static CorrelationAttributeInstance getCorrAttrForFile(AbstractFile file)
static void makeCorrAttrFromArtifactAttr(List< CorrelationAttributeInstance > corrAttrInstances, BlackboardArtifact artifact, ATTRIBUTE_TYPE artAttrType, int typeId)
CorrelationAttributeInstance getCorrelationAttributeInstance(CorrelationAttributeInstance.Type type, CorrelationCase correlationCase, CorrelationDataSource correlationDataSource, String value, String filePath)
static BlackboardArtifact getCorrAttrSourceArtifact(BlackboardArtifact artifact)
static List< CorrelationAttributeInstance > makeCorrAttrsToSave(BlackboardArtifact artifact)
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
static void makeCorrAttrFromAcctArtifact(List< CorrelationAttributeInstance > corrAttrInstances, BlackboardArtifact acctArtifact)
CorrelationAttributeInstance.Type getCorrelationTypeById(int typeId)
CentralRepoAccountType getAccountTypeByName(String accountTypeName)
CentralRepoAccount getOrCreateAccount(CentralRepoAccount.CentralRepoAccountType crAccountType, String accountUniqueID)

Copyright © 2012-2020 Basis Technology. Generated on: Wed Apr 8 2020
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.