2014-09-24 00:55:54 +00:00
|
|
|
#!/usr/bin/env python
|
2014-07-31 00:35:19 +00:00
|
|
|
# Copyright 2004-present Facebook. All Rights Reserved.
|
|
|
|
|
|
|
|
from __future__ import absolute_import
|
|
|
|
from __future__ import division
|
|
|
|
from __future__ import print_function
|
|
|
|
from __future__ import unicode_literals
|
|
|
|
|
|
|
|
import ast
|
|
|
|
import jinja2
|
|
|
|
import logging
|
|
|
|
import os
|
|
|
|
import sys
|
|
|
|
|
|
|
|
# set DEVELOPING to True for debug statements
|
|
|
|
DEVELOPING = False
|
|
|
|
|
|
|
|
# the log format for the logging module
|
|
|
|
LOG_FORMAT = "%(levelname)s [Line %(lineno)d]: %(message)s"
|
|
|
|
|
|
|
|
# IMPL_TEMPLATE is the jinja template used to generate the virtual table
|
|
|
|
# implementation file
|
|
|
|
IMPL_TEMPLATE = """// Copyright 2004-present Facebook. All Rights Reserved.
|
|
|
|
|
|
|
|
/*
|
|
|
|
** This file is generated. Do not modify it manually!
|
|
|
|
*/
|
|
|
|
|
2014-08-29 04:33:44 +00:00
|
|
|
#include <cstring>
|
2014-07-31 00:35:19 +00:00
|
|
|
#include <string>
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
#include <boost/lexical_cast.hpp>
|
|
|
|
|
2014-08-07 20:14:06 +00:00
|
|
|
#include "osquery/database.h"
|
2014-07-31 00:35:19 +00:00
|
|
|
#include "osquery/tables/base.h"
|
2014-09-06 01:12:37 +00:00
|
|
|
#include "osquery/registry/registry.h"
|
2014-07-31 00:35:19 +00:00
|
|
|
|
|
|
|
namespace osquery { namespace tables {
|
|
|
|
|
2014-09-21 21:27:09 +00:00
|
|
|
osquery::QueryData {{function}}();
|
2014-08-07 20:14:06 +00:00
|
|
|
|
2014-08-07 20:19:56 +00:00
|
|
|
struct sqlite3_{{table_name}} {
|
|
|
|
int n;
|
|
|
|
{% for col in schema %}\
|
|
|
|
std::vector<{{col.type}}> {{col.name}};
|
|
|
|
{% endfor %}\
|
|
|
|
};
|
|
|
|
|
2014-07-31 00:35:19 +00:00
|
|
|
const std::string
|
|
|
|
sqlite3_{{table_name}}_create_table_statement =
|
|
|
|
"CREATE TABLE {{table_name}}("
|
|
|
|
{% for col in schema %}\
|
2014-08-07 20:50:40 +00:00
|
|
|
"{{col.name}} \
|
|
|
|
{% if col.type == "std::string" %}VARCHAR{% endif %}\
|
|
|
|
{% if col.type == "int" %}INTEGER{% endif %}\
|
2014-07-31 00:35:19 +00:00
|
|
|
{% if not loop.last %}, {% endif %}"
|
|
|
|
{% endfor %}\
|
2014-08-07 20:50:40 +00:00
|
|
|
")";
|
2014-07-31 00:35:19 +00:00
|
|
|
|
2014-08-07 20:50:40 +00:00
|
|
|
int {{table_name_cc}}Create(
|
2014-07-31 00:35:19 +00:00
|
|
|
sqlite3 *db,
|
|
|
|
void *pAux,
|
|
|
|
int argc,
|
|
|
|
const char *const *argv,
|
|
|
|
sqlite3_vtab **ppVtab,
|
|
|
|
char **pzErr
|
|
|
|
) {
|
|
|
|
return xCreate<
|
|
|
|
x_vtab<sqlite3_{{table_name}}>,
|
|
|
|
sqlite3_{{table_name}}
|
|
|
|
>(
|
|
|
|
db, pAux, argc, argv, ppVtab, pzErr,
|
|
|
|
sqlite3_{{table_name}}_create_table_statement.c_str()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2014-08-07 20:50:40 +00:00
|
|
|
int {{table_name_cc}}Column(
|
2014-07-31 00:35:19 +00:00
|
|
|
sqlite3_vtab_cursor *cur,
|
|
|
|
sqlite3_context *ctx,
|
|
|
|
int col
|
|
|
|
) {
|
|
|
|
base_cursor *pCur = (base_cursor*)cur;
|
|
|
|
x_vtab<sqlite3_{{table_name}}> *pVtab =
|
|
|
|
(x_vtab<sqlite3_{{table_name}}>*)cur->pVtab;
|
|
|
|
|
|
|
|
if(pCur->row >= 0 && pCur->row < pVtab->pContent->n) {
|
|
|
|
switch (col) {
|
|
|
|
{% for col in schema %}\
|
|
|
|
// {{ col.name }}
|
|
|
|
case {{ loop.index0 }}:
|
|
|
|
{% if col.type == "std::string" %}\
|
|
|
|
sqlite3_result_text(
|
|
|
|
ctx,
|
|
|
|
(pVtab->pContent->{{col.name}}[pCur->row]).c_str(),
|
|
|
|
-1,
|
|
|
|
nullptr
|
|
|
|
);
|
|
|
|
{% endif %}\
|
|
|
|
{% if col.type == "int" %}\
|
|
|
|
sqlite3_result_int(
|
|
|
|
ctx,
|
|
|
|
(int)pVtab->pContent->{{col.name}}[pCur->row]
|
|
|
|
);
|
|
|
|
{% endif %}\
|
|
|
|
break;
|
|
|
|
{% endfor %}\
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return SQLITE_OK;
|
|
|
|
}
|
|
|
|
|
2014-08-07 20:50:40 +00:00
|
|
|
int {{table_name_cc}}Filter(
|
2014-07-31 00:35:19 +00:00
|
|
|
sqlite3_vtab_cursor *pVtabCursor,
|
|
|
|
int idxNum,
|
|
|
|
const char *idxStr,
|
|
|
|
int argc,
|
|
|
|
sqlite3_value **argv
|
|
|
|
) {
|
|
|
|
base_cursor *pCur = (base_cursor *)pVtabCursor;
|
|
|
|
x_vtab<sqlite3_{{table_name}}> *pVtab =
|
|
|
|
(x_vtab<sqlite3_{{table_name}}>*)pVtabCursor->pVtab;
|
|
|
|
|
|
|
|
pCur->row = 0;
|
2014-08-04 23:08:49 +00:00
|
|
|
{% for col in schema %}\
|
2014-08-29 04:33:44 +00:00
|
|
|
pVtab->pContent->{{col.name}}.clear();
|
2014-08-04 23:08:49 +00:00
|
|
|
{% endfor %}\
|
2014-07-31 00:35:19 +00:00
|
|
|
|
|
|
|
for (auto& row : osquery::tables::{{function}}()) {
|
|
|
|
{% for col in schema %}\
|
|
|
|
{% if col.type == "std::string" %}\
|
|
|
|
pVtab->pContent->{{col.name}}.push_back(row["{{col.name}}"]);
|
|
|
|
{% endif %}\
|
|
|
|
{% if col.type == "int" %}\
|
2014-08-14 23:24:28 +00:00
|
|
|
try {
|
|
|
|
pVtab->pContent->{{col.name}}\
|
2014-07-31 00:35:19 +00:00
|
|
|
.push_back(boost::lexical_cast<int>(row["{{col.name}}"]));
|
2014-08-14 23:24:28 +00:00
|
|
|
} catch (const boost::bad_lexical_cast& e) {
|
|
|
|
LOG(WARNING) << "Error casting " << row["{{col.name}}"] << "to int";
|
|
|
|
pVtab->pContent->{{col.name}}.push_back(-1);
|
|
|
|
}
|
2014-07-31 00:35:19 +00:00
|
|
|
{% endif %}\
|
|
|
|
{% endfor %}\
|
|
|
|
}
|
|
|
|
|
|
|
|
pVtab->pContent->n = pVtab->pContent->{{schema[0].name}}.size();
|
|
|
|
|
|
|
|
return SQLITE_OK;
|
|
|
|
}
|
|
|
|
|
2014-08-07 20:50:40 +00:00
|
|
|
static sqlite3_module {{table_name_cc}}Module = {
|
2014-08-07 20:19:56 +00:00
|
|
|
0,
|
2014-08-07 20:50:40 +00:00
|
|
|
{{table_name_cc}}Create,
|
|
|
|
{{table_name_cc}}Create,
|
2014-08-07 20:19:56 +00:00
|
|
|
xBestIndex,
|
|
|
|
xDestroy<x_vtab<sqlite3_{{table_name}}>>,
|
|
|
|
xDestroy<x_vtab<sqlite3_{{table_name}}>>,
|
|
|
|
xOpen<base_cursor>,
|
|
|
|
xClose<base_cursor>,
|
2014-08-07 20:50:40 +00:00
|
|
|
{{table_name_cc}}Filter,
|
2014-08-07 20:19:56 +00:00
|
|
|
xNext<base_cursor>,
|
|
|
|
xEof<base_cursor, x_vtab<sqlite3_{{table_name}}>>,
|
2014-08-07 20:50:40 +00:00
|
|
|
{{table_name_cc}}Column,
|
2014-08-07 20:19:56 +00:00
|
|
|
xRowid<base_cursor>,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
};
|
|
|
|
|
2014-08-07 20:50:40 +00:00
|
|
|
class {{table_name_cc}}TablePlugin : public TablePlugin {
|
2014-08-05 08:21:28 +00:00
|
|
|
public:
|
2014-08-07 20:50:40 +00:00
|
|
|
{{table_name_cc}}TablePlugin() {}
|
2014-08-05 08:21:28 +00:00
|
|
|
|
|
|
|
int attachVtable(sqlite3 *db) {
|
|
|
|
return sqlite3_attach_vtable<sqlite3_{{table_name}}>(
|
2014-08-07 20:50:40 +00:00
|
|
|
db, "{{table_name}}", &{{table_name_cc}}Module);
|
2014-08-05 08:21:28 +00:00
|
|
|
}
|
|
|
|
|
2014-08-07 20:50:40 +00:00
|
|
|
virtual ~{{table_name_cc}}TablePlugin() {}
|
2014-08-05 08:21:28 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
REGISTER_TABLE(
|
|
|
|
"{{table_name}}",
|
2014-08-07 20:50:40 +00:00
|
|
|
std::make_shared<{{table_name_cc}}TablePlugin>()
|
2014-08-05 08:21:28 +00:00
|
|
|
);
|
|
|
|
|
2014-07-31 00:35:19 +00:00
|
|
|
}}
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
def usage():
|
2014-08-07 20:50:40 +00:00
|
|
|
""" print program usage """
|
2014-09-24 08:58:12 +00:00
|
|
|
print("Usage: %s <spec.table> <file.cpp>" % sys.argv[0])
|
2014-07-31 00:35:19 +00:00
|
|
|
|
2014-08-07 20:50:40 +00:00
|
|
|
def to_camel_case(snake_case):
|
|
|
|
""" convert a snake_case string to camelCase """
|
|
|
|
components = snake_case.split('_')
|
|
|
|
return components[0] + "".join(x.title() for x in components[1:])
|
|
|
|
|
2014-07-31 00:35:19 +00:00
|
|
|
class Singleton(object):
|
|
|
|
"""
|
|
|
|
Make sure that anything that subclasses Singleton can only be instantiated
|
|
|
|
once
|
|
|
|
"""
|
|
|
|
|
|
|
|
_instance = None
|
|
|
|
|
|
|
|
def __new__(self, *args, **kwargs):
|
|
|
|
if not self._instance:
|
|
|
|
self._instance = super(Singleton, self).__new__(
|
|
|
|
self, *args, **kwargs)
|
|
|
|
return self._instance
|
|
|
|
|
|
|
|
class TableState(Singleton):
|
|
|
|
"""
|
|
|
|
Maintain the state of of the table commands during the execution of
|
|
|
|
the config file
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
self.table_name = ""
|
|
|
|
self.schema = []
|
|
|
|
self.header = ""
|
|
|
|
self.impl = ""
|
|
|
|
self.function = ""
|
|
|
|
|
2014-09-24 08:58:12 +00:00
|
|
|
def generate(self, path):
|
2014-07-31 00:35:19 +00:00
|
|
|
"""Generate the virtual table files"""
|
|
|
|
logging.debug("TableState.generate")
|
|
|
|
self.impl_content = jinja2.Template(IMPL_TEMPLATE).render(
|
|
|
|
table_name=self.table_name,
|
2014-08-07 20:50:40 +00:00
|
|
|
table_name_cc=to_camel_case(self.table_name),
|
2014-07-31 00:35:19 +00:00
|
|
|
schema=self.schema,
|
|
|
|
header=self.header,
|
|
|
|
impl=self.impl,
|
|
|
|
function=self.function,
|
|
|
|
)
|
|
|
|
|
2014-09-24 08:58:12 +00:00
|
|
|
path_bits = path.split("/")
|
|
|
|
for i in range(1, len(path_bits)):
|
|
|
|
dir_path = ""
|
|
|
|
for j in range(i):
|
|
|
|
dir_path += "%s/" % path_bits[j]
|
|
|
|
if not os.path.exists(dir_path):
|
|
|
|
os.mkdir(dir_path)
|
|
|
|
logging.debug("generating %s" % path)
|
|
|
|
with open(path, "w+") as file_h:
|
2014-07-31 00:35:19 +00:00
|
|
|
file_h.write(self.impl_content)
|
|
|
|
|
|
|
|
table = TableState()
|
|
|
|
|
|
|
|
class Column(object):
|
|
|
|
"""
|
|
|
|
A Column object to get around that fact that list literals in Python are
|
|
|
|
ordered but dictionaries aren't
|
|
|
|
"""
|
|
|
|
def __init__(self, **kwargs):
|
|
|
|
self.name = kwargs.get("name", "")
|
|
|
|
self.type = kwargs.get("type", "")
|
|
|
|
|
|
|
|
def table_name(name):
|
|
|
|
"""define the virtual table name"""
|
|
|
|
logging.debug("- table_name")
|
|
|
|
logging.debug(" - called with: %s" % name)
|
|
|
|
table.table_name = name
|
|
|
|
|
|
|
|
def schema(schema_list):
|
|
|
|
"""
|
|
|
|
define a list of Column object which represent the columns of your virtual
|
|
|
|
table
|
|
|
|
"""
|
|
|
|
logging.debug("- schema")
|
|
|
|
for col in schema_list:
|
|
|
|
logging.debug(" - %s (%s)" % (col.name, col.type))
|
|
|
|
table.schema = schema_list
|
|
|
|
|
|
|
|
def implementation(impl_string):
|
|
|
|
"""
|
|
|
|
define the path to the implementation file and the function which
|
|
|
|
implements the virtual table. You should use the following format:
|
|
|
|
|
|
|
|
# the path is "osquery/table/implementations/foo.cpp"
|
|
|
|
# the function is "QueryData genFoo();"
|
|
|
|
implementation("osquery/table/implementations/foo@genFoo")
|
|
|
|
"""
|
|
|
|
logging.debug("- implementation")
|
|
|
|
path, function = impl_string.split("@")
|
|
|
|
impl = "%s.cpp" % path
|
|
|
|
logging.debug(" - impl => %s" % impl)
|
|
|
|
logging.debug(" - function => %s" % function)
|
|
|
|
table.impl = impl
|
|
|
|
table.function = function
|
|
|
|
|
2014-08-19 08:26:51 +00:00
|
|
|
def main(argc, argv):
|
2014-07-31 00:35:19 +00:00
|
|
|
if DEVELOPING:
|
|
|
|
logging.basicConfig(format=LOG_FORMAT, level=logging.DEBUG)
|
|
|
|
else:
|
|
|
|
logging.basicConfig(format=LOG_FORMAT, level=logging.INFO)
|
|
|
|
|
2014-09-24 08:58:12 +00:00
|
|
|
if argc < 3:
|
2014-07-31 00:35:19 +00:00
|
|
|
usage()
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
filename = argv[1]
|
2014-09-24 08:58:12 +00:00
|
|
|
output = argv[2]
|
2014-07-31 00:35:19 +00:00
|
|
|
with open(filename, "rU") as file_handle:
|
|
|
|
tree = ast.parse(file_handle.read())
|
|
|
|
exec compile(tree, "<string>", "exec")
|
2014-09-24 08:58:12 +00:00
|
|
|
table.generate(output)
|
2014-07-31 00:35:19 +00:00
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2014-08-19 08:26:51 +00:00
|
|
|
main(len(sys.argv), sys.argv)
|