Merge pull request #1224 from theopolis/duti_table

OS X application duti/scheme listing table
This commit is contained in:
Mike Arpaia 2015-06-22 09:33:06 -07:00
commit 53c407781f
9 changed files with 272 additions and 83 deletions

View File

@ -51,6 +51,8 @@ namespace osquery {
#define BIGINT(x) boost::lexical_cast<std::string>(x)
/// See the affinity type documentation for TEXT.
#define UNSIGNED_BIGINT(x) boost::lexical_cast<std::string>(x)
/// See the affinity type documentation for TEXT.
#define DOUBLE(x) boost::lexical_cast<std::string>(x)
/**
* @brief The SQLite type affinities as represented as implementation literals.
@ -67,6 +69,8 @@ namespace osquery {
#define BIGINT_LITERAL long long int
/// See the literal type documentation for TEXT_LITERAL.
#define UNSIGNED_BIGINT_LITERAL unsigned long long int
/// See the literal type documentation for TEXT_LITERAL.
#define DOUBLE_LITERAL double
/// Cast an SQLite affinity type to the literal type.
#define AS_LITERAL(literal, value) boost::lexical_cast<literal>(value)
@ -89,7 +93,7 @@ enum ConstraintOperator : unsigned char {
GREATER_THAN_OR_EQUALS = 32
};
/// Type for flags for what constraint operators are admissable.
/// Type for flags for what constraint operators are admissible.
typedef unsigned char ConstraintOperatorFlag;
/// Flag for any operator type.
#define ANY_OP 0xFFU

View File

@ -172,7 +172,7 @@ Initializer::Initializer(int& argc, char**& argv, ToolType tool)
if (Flag::isDefault("database_path")) {
osquery::FLAGS_database_path = homedir + "/shell.db";
}
if (Flag::isDefault("extension_socket")) {
if (Flag::isDefault("extensions_socket")) {
osquery::FLAGS_extensions_socket = homedir + "/shell.em";
}
}

View File

@ -144,6 +144,16 @@ int xColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int col) {
<< ") to BIGINT";
}
sqlite3_result_int64(ctx, afinite);
} else if (type == "DOUBLE") {
double afinite;
try {
afinite = boost::lexical_cast<double>(value);
} catch (const boost::bad_lexical_cast &e) {
afinite = 0;
VLOG(1) << "Error casting" << column_name << " (" << value
<< ") to DOUBLE";
}
sqlite3_result_double(ctx, afinite);
}
return SQLITE_OK;

View File

@ -8,6 +8,8 @@
*
*/
#include <CoreServices/CoreServices.h>
#include <boost/algorithm/string/join.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp>
@ -18,6 +20,8 @@
#include <osquery/tables.h>
#include <osquery/sql.h>
#include "osquery/core/conversions.h"
namespace fs = boost::filesystem;
namespace pt = boost::property_tree;
@ -47,6 +51,110 @@ const std::vector<std::string> kHomeDirSearchPaths = {
"Applications", "Desktop", "Downloads",
};
enum AppSchemeFlags {
kSchemeNormal = 0,
// Default flag from the list of schemes on a default OS X 10.10 install.
kSchemeSystemDefault = 1,
// Protected flag from Apple Reference: Inter-app Communication
kSchemeProtected = 2,
};
const std::map<std::string, unsigned short> kApplicationSchemes = {
{"account", 0},
{"addressbook", kSchemeSystemDefault},
{"afp", kSchemeSystemDefault | kSchemeProtected},
{"aim", kSchemeSystemDefault},
{"alfred", 0},
{"alfredapp", 0},
{"app-prefs", 0},
{"applefeedback", kSchemeSystemDefault},
{"applescript", kSchemeSystemDefault},
{"apupdate", kSchemeSystemDefault},
{"at", kSchemeSystemDefault | kSchemeProtected},
{"atom", 0},
{"bluejeans", 0},
{"calinvite", 0},
{"calinvitelist", 0},
{"callto", 0},
{"calshow", 0},
{"cloudphoto", kSchemeSystemDefault},
{"conf", 0},
{"daap", kSchemeSystemDefault},
{"dict", kSchemeSystemDefault},
{"facetime", kSchemeSystemDefault | kSchemeProtected},
{"fb", kSchemeSystemDefault},
{"fbauth", 0},
{"file", kSchemeSystemDefault | kSchemeProtected},
{"ftp", kSchemeSystemDefault | kSchemeProtected},
{"gamecenter", kSchemeSystemDefault},
{"gopher", 0},
{"grammar", 0},
{"h323", 0},
{"help", kSchemeSystemDefault},
{"http", kSchemeSystemDefault | kSchemeProtected},
{"https", kSchemeSystemDefault | kSchemeProtected},
{"iadoptout", 0},
{"ibooks", kSchemeSystemDefault},
{"ical", kSchemeSystemDefault},
{"ichat", kSchemeSystemDefault},
{"icloud-sharing", kSchemeSystemDefault},
{"im", kSchemeSystemDefault},
{"imessage", kSchemeSystemDefault},
{"ipps", kSchemeSystemDefault},
{"irc", 0},
{"itls", kSchemeSystemDefault},
{"itms", kSchemeSystemDefault},
{"itms-books", kSchemeSystemDefault},
{"itms-bookss", kSchemeSystemDefault},
{"itmsp-app", 0},
{"itunesradio", kSchemeSystemDefault},
{"macappstore", kSchemeSystemDefault},
{"macappstores", kSchemeSystemDefault},
{"mailto", kSchemeSystemDefault | kSchemeProtected},
{"map", 0},
{"maps", kSchemeSystemDefault},
{"message", kSchemeSystemDefault},
{"messages", kSchemeSystemDefault},
{"ms-excel", 0},
{"ms-word", 0},
{"munki", 0},
{"news", kSchemeSystemDefault | kSchemeProtected},
{"nntp", 0},
{"nwnode", kSchemeSystemDefault | kSchemeProtected},
{"omnifocus", 0},
{"ophttp", 0},
{"pcast", kSchemeSystemDefault},
{"photos", kSchemeSystemDefault},
{"photos-event", 0},
{"photos-migrate-iphoto", 0},
{"photos-redirect", 0},
{"powerpoint", 0},
{"prefs", 0},
{"qs", 0},
{"qsinstall", 0},
{"qss-http", 0},
{"qssp-http", 0},
{"reminders", kSchemeSystemDefault},
{"rtsp", kSchemeSystemDefault},
{"shoebox", 0},
{"slack", 0},
{"smb", kSchemeSystemDefault | kSchemeProtected},
{"sms", kSchemeSystemDefault | kSchemeProtected},
{"ssh", kSchemeSystemDefault},
{"tel", kSchemeSystemDefault | kSchemeProtected},
{"telnet", kSchemeSystemDefault},
{"twitter", kSchemeSystemDefault},
{"txmt", 0},
{"vnc", kSchemeSystemDefault | kSchemeProtected},
{"wais", 0},
{"webapp", 0},
{"webcal", kSchemeSystemDefault},
{"whois", 0},
{"wunderlist", 0},
{"xmpp", kSchemeSystemDefault},
{"yelp", 0},
};
void genApplicationsFromPath(const fs::path& path,
std::vector<std::string>& apps) {
std::vector<std::string> new_apps;
@ -93,13 +201,20 @@ QueryData genApps(QueryContext& context) {
// Walk through several groups of common search paths that may contain apps.
std::vector<std::string> apps;
genApplicationsFromPath("/Applications", apps);
if (context.constraints["path"].exists(EQUALS)) {
auto app_constraints = context.constraints["path"].getAll(EQUALS);
for (const auto& app : app_constraints) {
apps.push_back((fs::path(app) / "Contents/Info.plist").string());
}
} else {
genApplicationsFromPath("/Applications", apps);
// List all users on the system, and walk common search paths with homes.
auto homes = osquery::getHomeDirectories();
for (const auto& home : homes) {
for (const auto& path : kHomeDirSearchPaths) {
genApplicationsFromPath(home / path, apps);
// List all users on the system, and walk common search paths with homes.
auto homes = osquery::getHomeDirectories();
for (const auto& home : homes) {
for (const auto& path : kHomeDirSearchPaths) {
genApplicationsFromPath(home / path, apps);
}
}
}
@ -110,11 +225,6 @@ QueryData genApps(QueryContext& context) {
// For each found application (path with an Info.plist) parse the plist.
for (const auto& path : apps) {
if (!context.constraints["path"].matches(path)) {
// Optimize by not searching when a path is a constraint.
continue;
}
if (!osquery::parsePlist(path, tree).ok()) {
TLOG << "Error parsing application plist: " << path;
continue;
@ -126,5 +236,73 @@ QueryData genApps(QueryContext& context) {
return results;
}
QueryData genAppSchemes(QueryContext& context) {
QueryData results;
for (const auto& scheme : kApplicationSchemes) {
auto protocol = scheme.first + "://";
auto cfprotocol = CFStringCreateWithCString(
kCFAllocatorDefault, protocol.c_str(), protocol.length());
if (cfprotocol == nullptr) {
continue;
}
// Create a "fake" URL that only contains the protocol component of a URI.
auto url = CFURLCreateWithString(kCFAllocatorDefault, cfprotocol, nullptr);
CFRelease(cfprotocol);
if (url == nullptr) {
continue;
}
// List all application bundles that request this protocol scheme.
auto apps = LSCopyApplicationURLsForURL(url, kLSRolesAll);
if (apps == nullptr) {
CFRelease(url);
continue;
}
// Check the default handler assigned to the protocol scheme.
auto default_app =
LSCopyDefaultApplicationURLForURL(url, kLSRolesAll, nullptr);
CFRelease(url);
for (CFIndex i = 0; i < CFArrayGetCount(apps); i++) {
Row r;
r["scheme"] = scheme.first;
auto app = CFArrayGetValueAtIndex(apps, i);
if (app == nullptr || CFGetTypeID(app) != CFURLGetTypeID()) {
// Handle problems with application listings.
continue;
}
auto path = CFURLCopyFileSystemPath((CFURLRef)app, kCFURLPOSIXPathStyle);
if (path == nullptr) {
continue;
}
r["handler"] = stringFromCFString(path);
CFRelease(path);
// Check if the handler is set (in the OS) as the default.
if (default_app != nullptr &&
CFEqual((CFTypeRef)app, (CFTypeRef)default_app)) {
r["enabled"] = "1";
} else {
r["enabled"] = "0";
}
r["external"] = (scheme.second & kSchemeSystemDefault) ? "0" : "1";
r["protected"] = (scheme.second & kSchemeProtected) ? "1" : "0";
results.push_back(r);
}
if (default_app != nullptr) {
CFRelease(default_app);
}
CFRelease(apps);
}
return results;
}
}
}

View File

@ -139,9 +139,9 @@ void parseWhereFrom(QueryData &results, const std::string &path) {
CFRelease(attributes);
}
void extractQuarantineProperty(const std::string table_key_name,
void extractQuarantineProperty(const std::string &table_key_name,
CFTypeRef property,
const std::string path,
const std::string &path,
QueryData &results) {
std::string value;
if (CFGetTypeID(property) == CFStringGetTypeID()) {

View File

@ -62,8 +62,8 @@ const std::map<CSSM_ACL_AUTHORIZATION_TAG, std::string> kACLAuthorizationTags =
{CSSM_ACL_AUTHORIZATION_CHANGE_OWNER, "change_owner"},
};
Status inline parseKeychainItemACLEntry(SecACLRef acl,
std::vector<KeychainItemACL> &acls) {
Status parseKeychainItemACLEntry(SecACLRef acl,
std::vector<KeychainItemACL> &acls) {
KeychainItemACL acl_data;
OSStatus os_status;
@ -105,21 +105,22 @@ Status inline parseKeychainItemACLEntry(SecACLRef acl,
app_index);
CFDataRef data = nullptr;
os_status = SecTrustedApplicationCopyData(app, &data);
if (os_status != noErr) {
return Status(os_status, "Coult not copy trusted application data");
if (os_status != noErr || data == nullptr) {
CFRelease(application_list);
if (data != nullptr) {
// To be very safe, assume data may have been allocated on error.
CFRelease(data);
}
return Status(os_status, "Could not copy trusted application data");
}
const UInt8 *bytes = CFDataGetBytePtr(data);
if (bytes && bytes[0] == 0x2f) {
if (bytes != nullptr && bytes[0] == '/') {
acl_data.applications.push_back(std::string((const char *)bytes));
}
if (data != nullptr) {
CFRelease(data);
}
}
if (application_list != nullptr) {
CFRelease(application_list);
CFRelease(data);
}
CFRelease(application_list);
}
acls.push_back(acl_data);
@ -131,7 +132,10 @@ Status parseKeychainItemACL(SecAccessRef access,
OSStatus os_status;
CFArrayRef acl_list = nullptr;
os_status = SecAccessCopyACLList(access, &acl_list);
if (os_status != noErr) {
if (os_status != noErr || acl_list == nullptr) {
if (acl_list != nullptr) {
CFRelease(acl_list);
}
return Status(os_status, "Could not copy ACL list");
}
@ -144,14 +148,12 @@ Status parseKeychainItemACL(SecAccessRef access,
continue;
}
}
if (acl_list != nullptr) {
CFRelease(acl_list);
}
CFRelease(acl_list);
return Status(0, "OK");
}
static std::string inline attributeBufferToString(const void *data, UInt32 length) {
static std::string attributeBufferToString(const void *data, UInt32 length) {
std::stringstream stream;
uint8 *p = (uint8 *)data;
while (length--) {
@ -165,10 +167,10 @@ static std::string inline attributeBufferToString(const void *data, UInt32 lengt
return stream.str();
}
Status inline genKeychainACLAppsForEntry(SecKeychainRef keychain,
SecKeychainItemRef item,
const std::string &path,
QueryData &results) {
Status genKeychainACLAppsForEntry(SecKeychainRef keychain,
SecKeychainItemRef item,
const std::string &path,
QueryData &results) {
KeychainItemMetadata item_metadata;
item_metadata.keychain_path = path;
@ -180,14 +182,16 @@ Status inline genKeychainACLAppsForEntry(SecKeychainRef keychain,
OSStatus os_status;
Status s;
os_status = SecKeychainItemCopyAccess(item, &access);
if (os_status == errSecNoAccessForItem) {
s = Status(1, "No ACLs for keychain item");
if (os_status == errSecNoAccessForItem || access == nullptr) {
if (access != nullptr) {
CFRelease(access);
}
return Status(os_status, "No ACLs for keychain item");
}
std::vector<KeychainItemACL> acl;
s = parseKeychainItemACL(access, acl);
if (access != nullptr) {
CFRelease(access);
}
CFRelease(access);
if (!s.ok()) {
return s;
}
@ -214,7 +218,7 @@ Status inline genKeychainACLAppsForEntry(SecKeychainRef keychain,
SecKeychainAttributeList *attr_list = nullptr;
os_status = SecKeychainItemCopyAttributesAndData(
item, info, &item_class, &attr_list, nullptr, nullptr);
if (os_status != noErr) {
if (os_status != noErr || attr_list == nullptr || info == nullptr) {
if (attr_list != nullptr) {
SecKeychainItemFreeAttributesAndData(attr_list, nullptr);
}
@ -225,21 +229,11 @@ Status inline genKeychainACLAppsForEntry(SecKeychainRef keychain,
"Could not copy attributes and data from the keychain");
}
Status copy_attr_status = Status(0, "OK");
if (info == nullptr || attr_list == nullptr) {
copy_attr_status = Status(1, "Could not get info or attr_list");
}
// Bail if the number of elements from the info/Attr list do not match.
if (info->count != attr_list->count) {
copy_attr_status = Status(1, "Info and attributes don't match");
}
if (!copy_attr_status.ok()) {
if (attr_list != nullptr) {
SecKeychainItemFreeAttributesAndData(attr_list, nullptr);
}
if (info != nullptr) {
SecKeychainFreeAttributeInfo(info);
}
return copy_attr_status;
SecKeychainItemFreeAttributesAndData(attr_list, nullptr);
SecKeychainFreeAttributeInfo(info);
return Status(1, "Info and attributes do not match");
}
for (int i = 0; i < info->count; ++i) {
@ -254,12 +248,10 @@ Status inline genKeychainACLAppsForEntry(SecKeychainRef keychain,
attributeBufferToString(attribute->data, attribute->length);
}
}
if (attr_list != nullptr) {
SecKeychainItemFreeAttributesAndData(attr_list, nullptr);
}
if (info != nullptr) {
SecKeychainFreeAttributeInfo(info);
}
// Finally, release/free the info/Attr lists.
SecKeychainItemFreeAttributesAndData(attr_list, nullptr);
SecKeychainFreeAttributeInfo(info);
for (const auto &acl_data : acl) {
for (const auto &app_path : acl_data.applications) {
@ -281,29 +273,24 @@ Status genKeychainACLApps(const std::string &path, QueryData &results) {
OSStatus os_status = 0;
os_status = SecKeychainOpen(path.c_str(), &keychain);
if (os_status != noErr) {
if (os_status != noErr || keychain == nullptr) {
if (keychain != nullptr) {
CFRelease(keychain);
}
return Status(os_status, "Could not open the keychain at " + path);
}
if (keychain == nullptr) {
return Status(1, "keychain object was not populated properly");
}
SecKeychainSearchRef search = nullptr;
OSQUERY_USE_DEPRECATED(os_status = SecKeychainSearchCreateFromAttributes(
keychain, CSSM_DL_DB_RECORD_ANY, NULL, &search););
if (os_status != noErr) {
if (keychain != nullptr) {
CFRelease(keychain);
if (os_status != noErr || search == nullptr) {
if (search != nullptr) {
CFRelease(search);
}
CFRelease(keychain);
return Status(os_status,
"Could not pull keychain items from the search API");
}
if (search == nullptr) {
if (keychain != nullptr) {
CFRelease(keychain);
}
return Status(1, "keychain search object was not populated properly");
}
SecKeychainItemRef item = nullptr;
while (true) {
@ -320,19 +307,15 @@ Status genKeychainACLApps(const std::string &path, QueryData &results) {
}
}
if (keychain != nullptr) {
CFRelease(keychain);
}
if (search != nullptr) {
CFRelease(search);
}
CFRelease(keychain);
CFRelease(search);
return Status(0, "OK");
}
QueryData genKeychainACLApps(QueryContext &context) {
QueryData results;
SecKeychainSetUserInteractionAllowed(false);
for (const auto &path : getKeychainPaths()) {
std::vector<std::string> ls_results;
auto list_status = listFilesInDirectory(path, ls_results, false);
@ -344,11 +327,12 @@ QueryData genKeychainACLApps(QueryContext &context) {
TLOG << "Checking directory: " << keychain;
auto gen_status = genKeychainACLApps(keychain, results);
if (!gen_status.ok()) {
TLOG << "Could not list keychain from " << keychain << ": "
TLOG << "Could not list items from " << keychain << ": "
<< gen_status.toString();
}
}
}
SecKeychainSetUserInteractionAllowed(true);
return results;
}

View File

@ -0,0 +1,12 @@
table_name("app_schemes")
description("OS X application schemes and handlers (e.g., http, file, mailto.")
schema([
Column("scheme", TEXT, "Name of the scheme/protocol"),
Column("handler", TEXT, "Application label for the handler"),
Column("enabled", INTEGER, "1 if this handler is the OS default, else 0"),
Column("external", INTEGER,
"1 if this handler does NOT exist on OS X by default, else 0"),
Column("protected", INTEGER,
"1 if this handler is protected (reserved) by OS X, else 0"),
])
implementation("apps@genAppSchemes")

View File

@ -5,7 +5,7 @@ schema([
Column("package_filename", TEXT, "Filename of original .pkg file"),
Column("version", TEXT, "Installed package version"),
Column("location", TEXT, "Optional relative install path on volume"),
Column("install_time", INTEGER, "Timestamp of install time"),
Column("install_time", DOUBLE, "Timestamp of install time"),
Column("installer_name", TEXT, "Name of installer process"),
Column("path", TEXT, "Path of receipt plist",
index=True, additional=True),

View File

@ -53,6 +53,7 @@ DATETIME = DataType("TEXT")
INTEGER = DataType("INTEGER", "int")
BIGINT = DataType("BIGINT", "long long int")
UNSIGNED_BIGINT = DataType("UNSIGNED_BIGINT", "long long unsigned int")
DOUBLE = DataType("DOUBLE", "double")
# Define table-category MACROS from the table specs
UNKNOWN = "UNKNOWN"