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

View File

@ -172,7 +172,7 @@ Initializer::Initializer(int& argc, char**& argv, ToolType tool)
if (Flag::isDefault("database_path")) { if (Flag::isDefault("database_path")) {
osquery::FLAGS_database_path = homedir + "/shell.db"; osquery::FLAGS_database_path = homedir + "/shell.db";
} }
if (Flag::isDefault("extension_socket")) { if (Flag::isDefault("extensions_socket")) {
osquery::FLAGS_extensions_socket = homedir + "/shell.em"; 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"; << ") to BIGINT";
} }
sqlite3_result_int64(ctx, afinite); 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; return SQLITE_OK;

View File

@ -8,6 +8,8 @@
* *
*/ */
#include <CoreServices/CoreServices.h>
#include <boost/algorithm/string/join.hpp> #include <boost/algorithm/string/join.hpp>
#include <boost/filesystem/operations.hpp> #include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp> #include <boost/filesystem/path.hpp>
@ -18,6 +20,8 @@
#include <osquery/tables.h> #include <osquery/tables.h>
#include <osquery/sql.h> #include <osquery/sql.h>
#include "osquery/core/conversions.h"
namespace fs = boost::filesystem; namespace fs = boost::filesystem;
namespace pt = boost::property_tree; namespace pt = boost::property_tree;
@ -47,6 +51,110 @@ const std::vector<std::string> kHomeDirSearchPaths = {
"Applications", "Desktop", "Downloads", "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, void genApplicationsFromPath(const fs::path& path,
std::vector<std::string>& apps) { std::vector<std::string>& apps) {
std::vector<std::string> new_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. // Walk through several groups of common search paths that may contain apps.
std::vector<std::string> 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. // List all users on the system, and walk common search paths with homes.
auto homes = osquery::getHomeDirectories(); auto homes = osquery::getHomeDirectories();
for (const auto& home : homes) { for (const auto& home : homes) {
for (const auto& path : kHomeDirSearchPaths) { for (const auto& path : kHomeDirSearchPaths) {
genApplicationsFromPath(home / path, apps); 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 each found application (path with an Info.plist) parse the plist.
for (const auto& path : apps) { 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()) { if (!osquery::parsePlist(path, tree).ok()) {
TLOG << "Error parsing application plist: " << path; TLOG << "Error parsing application plist: " << path;
continue; continue;
@ -126,5 +236,73 @@ QueryData genApps(QueryContext& context) {
return results; 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); CFRelease(attributes);
} }
void extractQuarantineProperty(const std::string table_key_name, void extractQuarantineProperty(const std::string &table_key_name,
CFTypeRef property, CFTypeRef property,
const std::string path, const std::string &path,
QueryData &results) { QueryData &results) {
std::string value; std::string value;
if (CFGetTypeID(property) == CFStringGetTypeID()) { 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"}, {CSSM_ACL_AUTHORIZATION_CHANGE_OWNER, "change_owner"},
}; };
Status inline parseKeychainItemACLEntry(SecACLRef acl, Status parseKeychainItemACLEntry(SecACLRef acl,
std::vector<KeychainItemACL> &acls) { std::vector<KeychainItemACL> &acls) {
KeychainItemACL acl_data; KeychainItemACL acl_data;
OSStatus os_status; OSStatus os_status;
@ -105,21 +105,22 @@ Status inline parseKeychainItemACLEntry(SecACLRef acl,
app_index); app_index);
CFDataRef data = nullptr; CFDataRef data = nullptr;
os_status = SecTrustedApplicationCopyData(app, &data); os_status = SecTrustedApplicationCopyData(app, &data);
if (os_status != noErr) { if (os_status != noErr || data == nullptr) {
return Status(os_status, "Coult not copy trusted application data"); 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); 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)); acl_data.applications.push_back(std::string((const char *)bytes));
} }
if (data != nullptr) { CFRelease(data);
CFRelease(data);
}
}
if (application_list != nullptr) {
CFRelease(application_list);
} }
CFRelease(application_list);
} }
acls.push_back(acl_data); acls.push_back(acl_data);
@ -131,7 +132,10 @@ Status parseKeychainItemACL(SecAccessRef access,
OSStatus os_status; OSStatus os_status;
CFArrayRef acl_list = nullptr; CFArrayRef acl_list = nullptr;
os_status = SecAccessCopyACLList(access, &acl_list); 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"); return Status(os_status, "Could not copy ACL list");
} }
@ -144,14 +148,12 @@ Status parseKeychainItemACL(SecAccessRef access,
continue; continue;
} }
} }
if (acl_list != nullptr) {
CFRelease(acl_list);
}
CFRelease(acl_list);
return Status(0, "OK"); 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; std::stringstream stream;
uint8 *p = (uint8 *)data; uint8 *p = (uint8 *)data;
while (length--) { while (length--) {
@ -165,10 +167,10 @@ static std::string inline attributeBufferToString(const void *data, UInt32 lengt
return stream.str(); return stream.str();
} }
Status inline genKeychainACLAppsForEntry(SecKeychainRef keychain, Status genKeychainACLAppsForEntry(SecKeychainRef keychain,
SecKeychainItemRef item, SecKeychainItemRef item,
const std::string &path, const std::string &path,
QueryData &results) { QueryData &results) {
KeychainItemMetadata item_metadata; KeychainItemMetadata item_metadata;
item_metadata.keychain_path = path; item_metadata.keychain_path = path;
@ -180,14 +182,16 @@ Status inline genKeychainACLAppsForEntry(SecKeychainRef keychain,
OSStatus os_status; OSStatus os_status;
Status s; Status s;
os_status = SecKeychainItemCopyAccess(item, &access); os_status = SecKeychainItemCopyAccess(item, &access);
if (os_status == errSecNoAccessForItem) { if (os_status == errSecNoAccessForItem || access == nullptr) {
s = Status(1, "No ACLs for keychain item"); if (access != nullptr) {
CFRelease(access);
}
return Status(os_status, "No ACLs for keychain item");
} }
std::vector<KeychainItemACL> acl; std::vector<KeychainItemACL> acl;
s = parseKeychainItemACL(access, acl); s = parseKeychainItemACL(access, acl);
if (access != nullptr) { CFRelease(access);
CFRelease(access);
}
if (!s.ok()) { if (!s.ok()) {
return s; return s;
} }
@ -214,7 +218,7 @@ Status inline genKeychainACLAppsForEntry(SecKeychainRef keychain,
SecKeychainAttributeList *attr_list = nullptr; SecKeychainAttributeList *attr_list = nullptr;
os_status = SecKeychainItemCopyAttributesAndData( os_status = SecKeychainItemCopyAttributesAndData(
item, info, &item_class, &attr_list, nullptr, nullptr); 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) { if (attr_list != nullptr) {
SecKeychainItemFreeAttributesAndData(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"); "Could not copy attributes and data from the keychain");
} }
Status copy_attr_status = Status(0, "OK"); // Bail if the number of elements from the info/Attr list do not match.
if (info == nullptr || attr_list == nullptr) {
copy_attr_status = Status(1, "Could not get info or attr_list");
}
if (info->count != attr_list->count) { if (info->count != attr_list->count) {
copy_attr_status = Status(1, "Info and attributes don't match"); SecKeychainItemFreeAttributesAndData(attr_list, nullptr);
} SecKeychainFreeAttributeInfo(info);
if (!copy_attr_status.ok()) { return Status(1, "Info and attributes do not match");
if (attr_list != nullptr) {
SecKeychainItemFreeAttributesAndData(attr_list, nullptr);
}
if (info != nullptr) {
SecKeychainFreeAttributeInfo(info);
}
return copy_attr_status;
} }
for (int i = 0; i < info->count; ++i) { for (int i = 0; i < info->count; ++i) {
@ -254,12 +248,10 @@ Status inline genKeychainACLAppsForEntry(SecKeychainRef keychain,
attributeBufferToString(attribute->data, attribute->length); attributeBufferToString(attribute->data, attribute->length);
} }
} }
if (attr_list != nullptr) {
SecKeychainItemFreeAttributesAndData(attr_list, nullptr); // Finally, release/free the info/Attr lists.
} SecKeychainItemFreeAttributesAndData(attr_list, nullptr);
if (info != nullptr) { SecKeychainFreeAttributeInfo(info);
SecKeychainFreeAttributeInfo(info);
}
for (const auto &acl_data : acl) { for (const auto &acl_data : acl) {
for (const auto &app_path : acl_data.applications) { for (const auto &app_path : acl_data.applications) {
@ -281,29 +273,24 @@ Status genKeychainACLApps(const std::string &path, QueryData &results) {
OSStatus os_status = 0; OSStatus os_status = 0;
os_status = SecKeychainOpen(path.c_str(), &keychain); 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); 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; SecKeychainSearchRef search = nullptr;
OSQUERY_USE_DEPRECATED(os_status = SecKeychainSearchCreateFromAttributes( OSQUERY_USE_DEPRECATED(os_status = SecKeychainSearchCreateFromAttributes(
keychain, CSSM_DL_DB_RECORD_ANY, NULL, &search);); keychain, CSSM_DL_DB_RECORD_ANY, NULL, &search););
if (os_status != noErr) { if (os_status != noErr || search == nullptr) {
if (keychain != nullptr) { if (search != nullptr) {
CFRelease(keychain); CFRelease(search);
} }
CFRelease(keychain);
return Status(os_status, return Status(os_status,
"Could not pull keychain items from the search API"); "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; SecKeychainItemRef item = nullptr;
while (true) { while (true) {
@ -320,19 +307,15 @@ Status genKeychainACLApps(const std::string &path, QueryData &results) {
} }
} }
if (keychain != nullptr) { CFRelease(keychain);
CFRelease(keychain); CFRelease(search);
}
if (search != nullptr) {
CFRelease(search);
}
return Status(0, "OK"); return Status(0, "OK");
} }
QueryData genKeychainACLApps(QueryContext &context) { QueryData genKeychainACLApps(QueryContext &context) {
QueryData results; QueryData results;
SecKeychainSetUserInteractionAllowed(false);
for (const auto &path : getKeychainPaths()) { for (const auto &path : getKeychainPaths()) {
std::vector<std::string> ls_results; std::vector<std::string> ls_results;
auto list_status = listFilesInDirectory(path, ls_results, false); auto list_status = listFilesInDirectory(path, ls_results, false);
@ -344,11 +327,12 @@ QueryData genKeychainACLApps(QueryContext &context) {
TLOG << "Checking directory: " << keychain; TLOG << "Checking directory: " << keychain;
auto gen_status = genKeychainACLApps(keychain, results); auto gen_status = genKeychainACLApps(keychain, results);
if (!gen_status.ok()) { if (!gen_status.ok()) {
TLOG << "Could not list keychain from " << keychain << ": " TLOG << "Could not list items from " << keychain << ": "
<< gen_status.toString(); << gen_status.toString();
} }
} }
} }
SecKeychainSetUserInteractionAllowed(true);
return results; 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("package_filename", TEXT, "Filename of original .pkg file"),
Column("version", TEXT, "Installed package version"), Column("version", TEXT, "Installed package version"),
Column("location", TEXT, "Optional relative install path on volume"), 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("installer_name", TEXT, "Name of installer process"),
Column("path", TEXT, "Path of receipt plist", Column("path", TEXT, "Path of receipt plist",
index=True, additional=True), index=True, additional=True),

View File

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