diff --git a/CMakeLists.txt b/CMakeLists.txt index f48abd8e..7ac1ff38 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,12 +6,6 @@ if(APPLE) set(OS_WHOLELINK_PRE "-Wl,-all_load") set(OS_WHOLELINK_POST "") else() - if(EXISTS "/etc/redhat-release") - set(CENTOS TRUE) - else() - set(UBUNTU TRUE) - endif() - set(OS_COMPILE_FLAGS "-std=c++11") if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") set(OS_COMPILE_FLAGS "${OS_COMPILE_FLAGS} -g") @@ -20,13 +14,17 @@ else() set(OS_WHOLELINK_POST "-Wl,-no-whole-archive") endif() -if(APPLE) - message("-- Building for OS X") -elseif(UBUNTU) - message("-- Building for Ubuntu") -elseif(CENTOS) - message("-- Building for CentOS") -endif() +# Use osquery language to set platform/os +execute_process( + COMMAND ${CMAKE_SOURCE_DIR}/tools/provision.sh get_platform + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE PLATFORM + OUTPUT_STRIP_TRAILING_WHITESPACE +) +string(REPLACE "." "_" PLATFORM "${PLATFORM}") +string(TOUPPER "${PLATFORM}" PLATFORM) +list(GET PLATFORM 0 OSQUERY_BUILD_OS) +list(GET PLATFORM 1 OSQUERY_BUILD_DISTRO) # Make sure deps were built before compiling execute_process( @@ -42,6 +40,16 @@ if(OSQUERY_DEPS_CHECK) message(WARNING "${Esc}[31m${OSQUERY_DEPS_MESSAGE}${Esc}[m") endif() +if(APPLE) + message("-- Building for OS X") +elseif(OSQUERY_BUILD_OS STREQUAL "UBUNTU") + set(UBUNTU TRUE) + message("-- Building for Ubuntu") +elseif(OSQUERY_BUILD_OS STREQUAL "CENTOS") + set(CENTOS TRUE) + message("-- Building for CentOS") +endif() + if(BUILD_SHARED_LIBS) set(USER_COMPILE_FLAGS "-fPIC") else() diff --git a/osquery/CMakeLists.txt b/osquery/CMakeLists.txt index 791981b6..21d197d6 100644 --- a/osquery/CMakeLists.txt +++ b/osquery/CMakeLists.txt @@ -90,7 +90,11 @@ execute_process( OUTPUT_STRIP_TRAILING_WHITESPACE ) -ADD_DEFINITIONS("-DOSQUERY_BUILD_VERSION=${OSQUERY_BUILD_VERSION}") +ADD_DEFINITIONS(" +-DOSQUERY_BUILD_VERSION=${OSQUERY_BUILD_VERSION} +-D${OSQUERY_BUILD_OS}=1 +-D${OSQUERY_BUILD_OS}_${OSQUERY_BUILD_DISTRO}=1 +") MACRO(ADD_OSQUERY_LINK LINK) list(APPEND OSQUERY_ADDITIONAL_LINKS ${LINK}) diff --git a/osquery/tables/specs/x/suid_bin.table b/osquery/tables/specs/x/suid_bin.table index 4e1523da..60d8ed36 100644 --- a/osquery/tables/specs/x/suid_bin.table +++ b/osquery/tables/specs/x/suid_bin.table @@ -1,8 +1,8 @@ table_name("suid_bin") schema([ Column(name="path", type="std::string"), - Column(name="unix_user", type="std::string"), - Column(name="unix_group", type="std::string"), + Column(name="username", type="std::string"), + Column(name="groupname", type="std::string"), Column(name="permissions", type="std::string"), ]) implementation("suid_bin@genSuidBin") diff --git a/osquery/tables/system/suid_bin.cpp b/osquery/tables/system/suid_bin.cpp index 55e7fd20..fa973b7a 100644 --- a/osquery/tables/system/suid_bin.cpp +++ b/osquery/tables/system/suid_bin.cpp @@ -1,27 +1,80 @@ // Copyright 2004-present Facebook. All Rights Reserved. #include + #include #include #include -#include -#include -#include -#include -#include "osquery/database.h" -using std::string; -using boost::lexical_cast; +#include +#include +#include + +#include + +#include "osquery/database.h" namespace osquery { namespace tables { -QueryData genSuidBin() { - Row r; - QueryData results; +Status genBin(const boost::filesystem::path& path, + int perms, + QueryData& results) { struct stat info; + // store user and group + if (stat(path.c_str(), &info) != 0) { + return Status(1, "stat failed"); + } + + // store path + Row r; + r["path"] = path.string(); + + struct passwd *pw = getpwuid(info.st_uid); + struct group *gr = getgrgid(info.st_gid); + + // get user name + group + std::string user; + if (pw != nullptr) { + user = std::string(pw->pw_name); + } else { + user = boost::lexical_cast(info.st_uid); + } + + std::string group; + if (gr != nullptr) { + group = std::string(gr->gr_name); + } else { + group = boost::lexical_cast(info.st_gid); + } + + r["username"] = user; + r["groupname"] = group; + + r["permissions"] = ""; + if ((perms & 04000) == 04000) { + r["permissions"] += "S"; + } + + if ((perms & 02000) == 02000) { + r["permissions"] += "G"; + } + + results.push_back(r); + return Status(0, "OK"); +} + +QueryData genSuidBin() { + QueryData results; boost::system::error_code error; +#if defined(UBUNTU) + // When building on supported Ubuntu systems, boost may ABRT. + if (geteuid() != 0) { + return results; + } +#endif + boost::filesystem::recursive_directory_iterator it = boost::filesystem::recursive_directory_iterator( boost::filesystem::path("/"), error); @@ -35,32 +88,10 @@ QueryData genSuidBin() { while (it != end) { boost::filesystem::path path = *it; try { - if (boost::filesystem::is_regular_file(path) && - ((it.status().permissions() & 04000) == 04000 || - (it.status().permissions() & 02000) == 02000)) { - // store path - r["path"] = boost::lexical_cast(path); - - // store user and group - if (stat(path.c_str(), &info) == 0) { - struct passwd *pw = getpwuid(info.st_uid); - struct group *gr = getgrgid(info.st_gid); - // get user name - r["unix_user"] = pw ? boost::lexical_cast(pw->pw_name) - : boost::lexical_cast(info.st_uid); - // get group - r["unix_group"] = gr ? boost::lexical_cast(gr->gr_name) - : boost::lexical_cast(info.st_gid); - - // get permission - r["permissions"] = ""; - r["permissions"] += - (it.status().permissions() & 04000) == 04000 ? "S" : ""; - r["permissions"] += - (it.status().permissions() & 02000) == 02000 ? "G" : ""; - - results.push_back(r); - } + int perms = it.status().permissions(); + if (boost::filesystem::is_regular_file(path) && + ((perms & 04000) == 04000 || (perms & 02000) == 02000)) { + genBin(path, perms, results); } } catch (...) { // handle invalid files like /dev/fd/3 diff --git a/osquery/tables/templates/blacklist.cpp.in b/osquery/tables/templates/blacklist.cpp.in new file mode 100644 index 00000000..68a3f144 --- /dev/null +++ b/osquery/tables/templates/blacklist.cpp.in @@ -0,0 +1,7 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +/* +** This file is generated. Do not modify it manually! +*/ + +void __blacklisted_{{table_name}}() {} diff --git a/osquery/tables/templates/default.cpp.in b/osquery/tables/templates/default.cpp.in new file mode 100644 index 00000000..42918f6c --- /dev/null +++ b/osquery/tables/templates/default.cpp.in @@ -0,0 +1,196 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +/* +** This file is generated. Do not modify it manually! +*/ + +#include +#include +#include + +#include + +#include "osquery/database.h" +#include "osquery/tables/base.h" +#include "osquery/registry/registry.h" + +namespace osquery { namespace tables { + +{% if class_name == "" %}\ +osquery::QueryData {{function}}(); +{% else %} +class {{class_name}} { + public: + static osquery::QueryData {{function}}(); +}; +{% endif %}\ + +struct sqlite3_{{table_name}} { + int n; +{% for col in schema %}\ + std::vector<{{col.type}}> {{col.name}}; +{% endfor %}\ +}; + +const std::string + sqlite3_{{table_name}}_create_table_statement = + "CREATE TABLE {{table_name}}(" + {% for col in schema %}\ + "{{col.name}} \ +{% if col.type == "std::string" %}VARCHAR{% endif %}\ +{% if col.type == "int" %}INTEGER{% endif %}\ +{% if col.type == "long long int" %}BIGINT{% endif %}\ +{% if not loop.last %}, {% endif %}" + {% endfor %}\ +")"; + +int {{table_name_cc}}Create( + sqlite3 *db, + void *pAux, + int argc, + const char *const *argv, + sqlite3_vtab **ppVtab, + char **pzErr +) { + return xCreate< + x_vtab, + sqlite3_{{table_name}} + >( + db, pAux, argc, argv, ppVtab, pzErr, + sqlite3_{{table_name}}_create_table_statement.c_str() + ); +} + +int {{table_name_cc}}Column( + sqlite3_vtab_cursor *cur, + sqlite3_context *ctx, + int col +) { + base_cursor *pCur = (base_cursor*)cur; + x_vtab *pVtab = + (x_vtab*)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 %}\ +{% if col.type == "long long int" %}\ + sqlite3_result_int64( + ctx, + (long long int)pVtab->pContent->{{col.name}}[pCur->row] + ); +{% endif %}\ + break; +{% endfor %}\ + } + } + return SQLITE_OK; +} + +int {{table_name_cc}}Filter( + sqlite3_vtab_cursor *pVtabCursor, + int idxNum, + const char *idxStr, + int argc, + sqlite3_value **argv +) { + base_cursor *pCur = (base_cursor *)pVtabCursor; + x_vtab *pVtab = + (x_vtab*)pVtabCursor->pVtab; + + pCur->row = 0; +{% for col in schema %}\ + pVtab->pContent->{{col.name}}.clear(); +{% endfor %}\ + +{% if class_name != "" %} + for (auto& row : osquery::tables::{{class_name}}::{{function}}()) { +{% else %} + for (auto& row : osquery::tables::{{function}}()) { +{% endif %} +{% for col in schema %}\ +{% if col.type == "std::string" %}\ + pVtab->pContent->{{col.name}}.push_back(row["{{col.name}}"]); +{% endif %}\ +{% if col.type == "int" %}\ + try { + pVtab->pContent->{{col.name}}\ +.push_back(boost::lexical_cast(row["{{col.name}}"])); + } catch (const boost::bad_lexical_cast& e) { + LOG(WARNING) << "Error casting " << row["{{col.name}}"] << " to int"; + pVtab->pContent->{{col.name}}.push_back(-1); + } +{% endif %}\ +{% if col.type == "long long int" %}\ + try { + pVtab->pContent->{{col.name}}\ +.push_back(boost::lexical_cast(row["{{col.name}}"])); + } catch (const boost::bad_lexical_cast& e) { + LOG(WARNING) << "Error casting " << row["{{col.name}}"] << " to long long int"; + pVtab->pContent->{{col.name}}.push_back(-1); + } +{% endif %}\ +{% endfor %}\ + } + + pVtab->pContent->n = pVtab->pContent->{{schema[0].name}}.size(); + + return SQLITE_OK; +} + +static sqlite3_module {{table_name_cc}}Module = { + 0, + {{table_name_cc}}Create, + {{table_name_cc}}Create, + xBestIndex, + xDestroy>, + xDestroy>, + xOpen, + xClose, + {{table_name_cc}}Filter, + xNext, + xEof>, + {{table_name_cc}}Column, + xRowid, + 0, + 0, + 0, + 0, + 0, + 0, + 0, +}; + +class {{table_name_cc}}TablePlugin : public TablePlugin { +public: + {{table_name_cc}}TablePlugin() {} + + int attachVtable(sqlite3 *db) { + return sqlite3_attach_vtable( + db, "{{table_name}}", &{{table_name_cc}}Module); + } + + virtual ~{{table_name_cc}}TablePlugin() {} +}; + +REGISTER_TABLE( + "{{table_name}}", + std::make_shared<{{table_name_cc}}TablePlugin>() +); + +}} diff --git a/tools/gentable.py b/tools/gentable.py index a49f893d..e16a8dfe 100755 --- a/tools/gentable.py +++ b/tools/gentable.py @@ -18,218 +18,11 @@ DEVELOPING = False # the log format for the logging module LOG_FORMAT = "%(levelname)s [Line %(lineno)d]: %(message)s" -# BL_IMPL_TEMPLATE is the jinja template used to generate the virtual table -# implementation file when the table is blacklisted in ./osquery/tables/specs -BL_IMPL_TEMPLATE = """// Copyright 2004-present Facebook. All Rights Reserved. +# Read all implementation templates +TEMPLATES = {} -/* -** This file is generated. Do not modify it manually! -*/ - -void __blacklisted_{{table_name}}() {} - -""" - -# 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! -*/ - -#include -#include -#include - -#include - -#include "osquery/database.h" -#include "osquery/tables/base.h" -#include "osquery/registry/registry.h" - -namespace osquery { namespace tables { - -{% if class_name == "" %} -osquery::QueryData {{function}}(); -{% else %} -class {{class_name}} { - public: - static osquery::QueryData {{function}}(); -}; -{% endif %} - -struct sqlite3_{{table_name}} { - int n; -{% for col in schema %}\ - std::vector<{{col.type}}> {{col.name}}; -{% endfor %}\ -}; - -const std::string - sqlite3_{{table_name}}_create_table_statement = - "CREATE TABLE {{table_name}}(" - {% for col in schema %}\ - "{{col.name}} \ -{% if col.type == "std::string" %}VARCHAR{% endif %}\ -{% if col.type == "int" %}INTEGER{% endif %}\ -{% if col.type == "long long int" %}BIGINT{% endif %}\ -{% if not loop.last %}, {% endif %}" - {% endfor %}\ -")"; - -int {{table_name_cc}}Create( - sqlite3 *db, - void *pAux, - int argc, - const char *const *argv, - sqlite3_vtab **ppVtab, - char **pzErr -) { - return xCreate< - x_vtab, - sqlite3_{{table_name}} - >( - db, pAux, argc, argv, ppVtab, pzErr, - sqlite3_{{table_name}}_create_table_statement.c_str() - ); -} - -int {{table_name_cc}}Column( - sqlite3_vtab_cursor *cur, - sqlite3_context *ctx, - int col -) { - base_cursor *pCur = (base_cursor*)cur; - x_vtab *pVtab = - (x_vtab*)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 %}\ -{% if col.type == "long long int" %}\ - sqlite3_result_int64( - ctx, - (long long int)pVtab->pContent->{{col.name}}[pCur->row] - ); -{% endif %}\ - break; -{% endfor %}\ - } - } - return SQLITE_OK; -} - -int {{table_name_cc}}Filter( - sqlite3_vtab_cursor *pVtabCursor, - int idxNum, - const char *idxStr, - int argc, - sqlite3_value **argv -) { - base_cursor *pCur = (base_cursor *)pVtabCursor; - x_vtab *pVtab = - (x_vtab*)pVtabCursor->pVtab; - - pCur->row = 0; -{% for col in schema %}\ - pVtab->pContent->{{col.name}}.clear(); -{% endfor %}\ - -{% if class_name != "" %} - for (auto& row : osquery::tables::{{class_name}}::{{function}}()) { -{% else %} - for (auto& row : osquery::tables::{{function}}()) { -{% endif %} -{% for col in schema %}\ -{% if col.type == "std::string" %}\ - pVtab->pContent->{{col.name}}.push_back(row["{{col.name}}"]); -{% endif %}\ -{% if col.type == "int" %}\ - try { - pVtab->pContent->{{col.name}}\ -.push_back(boost::lexical_cast(row["{{col.name}}"])); - } catch (const boost::bad_lexical_cast& e) { - LOG(WARNING) << "Error casting " << row["{{col.name}}"] << " to int"; - pVtab->pContent->{{col.name}}.push_back(-1); - } -{% endif %}\ -{% if col.type == "long long int" %}\ - try { - pVtab->pContent->{{col.name}}\ -.push_back(boost::lexical_cast(row["{{col.name}}"])); - } catch (const boost::bad_lexical_cast& e) { - LOG(WARNING) << "Error casting " << row["{{col.name}}"] << " to long long int"; - pVtab->pContent->{{col.name}}.push_back(-1); - } -{% endif %}\ -{% endfor %}\ - } - - pVtab->pContent->n = pVtab->pContent->{{schema[0].name}}.size(); - - return SQLITE_OK; -} - -static sqlite3_module {{table_name_cc}}Module = { - 0, - {{table_name_cc}}Create, - {{table_name_cc}}Create, - xBestIndex, - xDestroy>, - xDestroy>, - xOpen, - xClose, - {{table_name_cc}}Filter, - xNext, - xEof>, - {{table_name_cc}}Column, - xRowid, - 0, - 0, - 0, - 0, - 0, - 0, - 0, -}; - -class {{table_name_cc}}TablePlugin : public TablePlugin { -public: - {{table_name_cc}}TablePlugin() {} - - int attachVtable(sqlite3 *db) { - return sqlite3_attach_vtable( - db, "{{table_name}}", &{{table_name_cc}}Module); - } - - virtual ~{{table_name_cc}}TablePlugin() {} -}; - -REGISTER_TABLE( - "{{table_name}}", - std::make_shared<{{table_name_cc}}TablePlugin>() -); - -}} - -""" +# Temporary reserved column names +RESERVED = ["group"] def usage(): """ print program usage """ @@ -243,6 +36,35 @@ def to_camel_case(snake_case): def lightred(msg): return "\033[1;31m %s \033[0m" % str(msg) +def is_blacklisted(path, table_name): + """Allow blacklisting by tablename.""" + specs_path = os.path.dirname(os.path.dirname(path)) + blacklist_path = os.path.join(specs_path, "blacklist") + if not os.path.exists(blacklist_path): + return False + try: + with open(blacklist_path, "r") as fh: + blacklist = [line.strip() for line in fh.read().split("\n") + if len(line.strip()) > 0 and line.strip()[0] != "#"] + if table_name in blacklist: + return True + except: + # Blacklist is not readable. + pass + return False + +def setup_templates(path): + tables_path = os.path.dirname(os.path.dirname(os.path.dirname(path))) + templates_path = os.path.join(tables_path, "templates") + if not os.path.exists(templates_path): + print ("Cannot read templates path: %s" % (templates_path)) + exit(1) + for template in os.listdir(os.path.join(tables_path, "templates")): + template_name = template.split(".", 1)[0] + with open(os.path.join(templates_path, template), "rb") as fh: + TEMPLATES[template_name] = fh.read().replace("\\\n", "") + pass + class Singleton(object): """ Make sure that anything that subclasses Singleton can only be instantiated @@ -278,10 +100,10 @@ class TableState(Singleton): def foreign_keys(self): return [i for i in self.schema if isinstance(i, ForeignKey)] - def generate(self, path, template=IMPL_TEMPLATE): + def generate(self, path, template="default"): """Generate the virtual table files""" logging.debug("TableState.generate") - self.impl_content = jinja2.Template(template).render( + self.impl_content = jinja2.Template(TEMPLATES[template]).render( table_name=self.table_name, table_name_cc=to_camel_case(self.table_name), schema=self.columns(), @@ -291,6 +113,14 @@ class TableState(Singleton): class_name=self.class_name ) + # Check for reserved column names + for column in self.columns(): + if column.name in RESERVED: + print (lightred(("Cannot use column name: %s in table: %s " + "(the column name is reserved)" % ( + column.name, self.table_name)))) + exit(1) + path_bits = path.split("/") for i in range(1, len(path_bits)): dir_path = "" @@ -305,7 +135,7 @@ class TableState(Singleton): def blacklist(self, path): print (lightred("Blacklisting generated %s" % path)) logging.debug("blacklisting %s" % path) - self.generate(path, template=BL_IMPL_TEMPLATE) + self.generate(path, template="blacklist") table = TableState() @@ -374,23 +204,6 @@ def implementation(impl_string): def description(text): table.description = text -def is_blacklisted(path, table_name): - """Allow blacklisting by tablename.""" - specs_path = os.path.dirname(os.path.dirname(path)) - blacklist_path = os.path.join(specs_path, "blacklist") - if not os.path.exists(blacklist_path): - return False - try: - with open(blacklist_path, "r") as fh: - blacklist = [line.strip() for line in fh.read().split("\n") - if len(line.strip()) > 0 and line.strip()[0] != "#"] - if table_name in blacklist: - return True - except: - # Blacklist is not readable. - pass - return False - def main(argc, argv): if DEVELOPING: logging.basicConfig(format=LOG_FORMAT, level=logging.DEBUG) @@ -407,6 +220,7 @@ def main(argc, argv): # Adding a 3rd parameter will enable the blacklist disable_blacklist = argc > 3 + setup_templates(filename) with open(filename, "rU") as file_handle: tree = ast.parse(file_handle.read()) exec(compile(tree, "", "exec")) diff --git a/tools/lib.sh b/tools/lib.sh index 8bb4400d..2577cdf0 100755 --- a/tools/lib.sh +++ b/tools/lib.sh @@ -4,13 +4,26 @@ function platform() { local __resultvar=$1 if [[ -f "/etc/redhat-release" ]]; then eval $__resultvar="centos" - elif [[ -f "/etc/debian_version" ]]; then + elif [[ -f "/etc/lsb-release" ]]; then eval $__resultvar="ubuntu" elif [[ -f "/etc/pf.conf" ]]; then eval $__resultvar="darwin" fi } +function distro() { + local __resultvar=$2 + if [[ $1 = "centos" ]]; then + eval $__resultvar="centos"`cat /etc/redhat-release | awk '{print $3}'` + elif [[ $1 = "ubuntu" ]]; then + eval $__resultvar=`cat /etc/*-release | grep DISTRIB_CODENAME | awk -F '=' '{print $2}'` + elif [[ $1 = "darwin" ]]; then + eval $__resultvar=`sw_vers -productVersion | awk -F '.' '{print $1 "." $2}'` + else + eval $__resultvar="unknown_version" + fi +} + function threads() { local __resultvar=$1 platform OS @@ -53,3 +66,4 @@ function contains_element() { for e in "${@:2}"; do [[ "$e" == "$1" ]] && return 0; done return 1 } + diff --git a/tools/provision.sh b/tools/provision.sh index 2a2b52ba..87f70f93 100755 --- a/tools/provision.sh +++ b/tools/provision.sh @@ -241,13 +241,15 @@ function check() { HASH=`sha1sum $0 | awk '{print $1}'` fi - if [[ ! "$1" = "check" ]]; then + if [[ "$1" = "build" ]]; then echo $HASH > "$2/.provision" return + elif [[ ! "$1" = "check" ]]; then + return fi if [[ "$#" < 2 ]]; then - echo "Usage: $0 check BUILD_PATH" + echo "Usage: $0 (check|build) BUILD_PATH" exit 1 fi @@ -261,17 +263,22 @@ function check() { function main() { platform OS + distro $OS DISTRO + + if [[ $1 = "get_platform" ]]; then + echo "$OS;$DISTRO" + return 0 + fi mkdir -p "$WORKING_DIR" cd "$WORKING_DIR" if [[ $OS = "centos" ]]; then - log "detected centos" + log "detected centos ($DISTRO)" elif [[ $OS = "ubuntu" ]]; then - log "detected ubuntu" - DISTRO=`cat /etc/*-release | grep DISTRIB_CODENAME | awk '{split($0,bits,"="); print bits[2]}'` + log "detected ubuntu ($DISTRO)" elif [[ $OS = "darwin" ]]; then - log "detected mac os x" + log "detected mac os x ($DISTRO)" else fatal "could not detect the current operating system. exiting." fi @@ -445,4 +452,4 @@ function main() { } check $1 $2 -main +main $1