| /* |
| * Media contexts backend for DB objects |
| * |
| * Author: KaiGai Kohei <kaigai@ak.jp.nec.com> |
| */ |
| |
| #include <sys/stat.h> |
| #include <string.h> |
| #include <stdio.h> |
| #include <stdio_ext.h> |
| #include <ctype.h> |
| #include <errno.h> |
| #include <limits.h> |
| #include <fnmatch.h> |
| #include "callbacks.h" |
| #include "label_internal.h" |
| |
| /* |
| * Regular database object's security context interface |
| * |
| * It provides applications a regular security context for the given |
| * database objects. The pair of object's name and a security context |
| * are described in the specfile. In the default, it shall be stored |
| * in the /etc/selinux/$POLICYTYPE/contexts/sepgsql_contexts . |
| * (It assumes SE-PostgreSQL in the default. For other RDBMS, use the |
| * SELABEL_OPT_PATH option to specify different specfile.) |
| * |
| * Each line has the following format: |
| * <object class> <object name/identifier> <security context> |
| * |
| * For example: |
| * ---------------------------------------- |
| * # |
| * # It is an example specfile for database obejcts |
| * # |
| * db_database template1 system_u:object_r:sepgsql_db_t:s0 |
| * |
| * db_schema *.pg_catalog system_u:object_r:sepgsql_sys_schema_t:s0 |
| * |
| * db_table *.pg_catalog.* system_u:object_r:sepgsql_sysobj_t:s0 |
| * db_column *.pg_catalog.*.* system_u:object_r:sepgsql_sysobj_t:s0 |
| * ---------------------------------------- |
| * |
| * All the characters after the '#' are dealt as comments. |
| * |
| * The first token is object class. SELABEL_DB_* declared in label.h are |
| * corresponding to a certain database object. |
| * |
| * The object name/identifier is compared to the given key. |
| * A database object can have its own namespace hierarchy. |
| * In the case of SE-PgSQL, database is the top level object, and schema |
| * is deployed just under a database. A schema can contains various kind |
| * of objects, such as tables, procedures and so on. |
| * Thus, when we lookup an expected security context for a table of |
| * "pg_class", it is necessary to assume selabel_lookup() is called with |
| * "postgres.pg_catalog.pg_class", not just a "pg_class". |
| * |
| * Wildcards ('*' or '?') are available on the patterns, so if you want |
| * to match a table within any schema, you should set '*' on the upper |
| * namespaces of the table. |
| * |
| * The structure of namespace depends on RDBMS. |
| * For example, Trusted-RUBIX has an idea of "catalog" which performs |
| * as a namespace between a database and individual schemas. In this |
| * case, a table has upper three layers. |
| */ |
| |
| /* |
| * spec_t : It holds a pair of a key and an expected security context |
| */ |
| typedef struct spec { |
| struct selabel_lookup_rec lr; |
| char *key; |
| int type; |
| int matches; |
| } spec_t; |
| |
| /* |
| * catalog_t : An array of spec_t |
| */ |
| typedef struct catalog { |
| unsigned int nspec; /* number of specs in use */ |
| unsigned int limit; /* physical limitation of specs[] */ |
| spec_t specs[0]; |
| } catalog_t; |
| |
| /* |
| * Helper function to parse a line read from the specfile |
| */ |
| static int |
| process_line(const char *path, char *line_buf, unsigned int line_num, |
| catalog_t *catalog) |
| { |
| spec_t *spec = &catalog->specs[catalog->nspec]; |
| char *type, *key, *context, *temp; |
| int items; |
| |
| /* Cut off comments */ |
| temp = strchr(line_buf, '#'); |
| if (temp) |
| *temp = '\0'; |
| |
| /* |
| * Every entry must have the following format |
| * <object class> <object name> <security context> |
| */ |
| type = key = context = temp = NULL; |
| items = sscanf(line_buf, "%ms %ms %ms %ms", |
| &type, &key, &context, &temp); |
| if (items != 3) { |
| if (items > 0) |
| selinux_log(SELINUX_WARNING, |
| "%s: line %u has invalid format, skipped", |
| path, line_num); |
| goto skip; |
| } |
| |
| /* |
| * Set up individual spec entry |
| */ |
| memset(spec, 0, sizeof(spec_t)); |
| |
| if (!strcmp(type, "db_database")) |
| spec->type = SELABEL_DB_DATABASE; |
| else if (!strcmp(type, "db_schema")) |
| spec->type = SELABEL_DB_SCHEMA; |
| else if (!strcmp(type, "db_table")) |
| spec->type = SELABEL_DB_TABLE; |
| else if (!strcmp(type, "db_column")) |
| spec->type = SELABEL_DB_COLUMN; |
| else if (!strcmp(type, "db_sequence")) |
| spec->type = SELABEL_DB_SEQUENCE; |
| else if (!strcmp(type, "db_view")) |
| spec->type = SELABEL_DB_VIEW; |
| else if (!strcmp(type, "db_procedure")) |
| spec->type = SELABEL_DB_PROCEDURE; |
| else if (!strcmp(type, "db_blob")) |
| spec->type = SELABEL_DB_BLOB; |
| else if (!strcmp(type, "db_tuple")) |
| spec->type = SELABEL_DB_TUPLE; |
| else if (!strcmp(type, "db_language")) |
| spec->type = SELABEL_DB_LANGUAGE; |
| else if (!strcmp(type, "db_exception")) |
| spec->type = SELABEL_DB_EXCEPTION; |
| else if (!strcmp(type, "db_datatype")) |
| spec->type = SELABEL_DB_DATATYPE; |
| else { |
| selinux_log(SELINUX_WARNING, |
| "%s: line %u has invalid object type %s\n", |
| path, line_num, type); |
| goto skip; |
| } |
| |
| free(type); |
| spec->key = key; |
| spec->lr.ctx_raw = context; |
| |
| catalog->nspec++; |
| |
| return 0; |
| |
| skip: |
| free(type); |
| free(key); |
| free(context); |
| free(temp); |
| |
| return 0; |
| } |
| |
| /* |
| * selabel_close() handler |
| */ |
| static void |
| db_close(struct selabel_handle *rec) |
| { |
| catalog_t *catalog = (catalog_t *)rec->data; |
| spec_t *spec; |
| unsigned int i; |
| |
| for (i = 0; i < catalog->nspec; i++) { |
| spec = &catalog->specs[i]; |
| free(spec->key); |
| free(spec->lr.ctx_raw); |
| free(spec->lr.ctx_trans); |
| } |
| free(catalog); |
| } |
| |
| /* |
| * selabel_lookup() handler |
| */ |
| static struct selabel_lookup_rec * |
| db_lookup(struct selabel_handle *rec, const char *key, int type) |
| { |
| catalog_t *catalog = (catalog_t *)rec->data; |
| spec_t *spec; |
| unsigned int i; |
| |
| for (i = 0; i < catalog->nspec; i++) { |
| spec = &catalog->specs[i]; |
| |
| if (spec->type != type) |
| continue; |
| if (!fnmatch(spec->key, key, 0)) { |
| spec->matches++; |
| |
| return &spec->lr; |
| } |
| } |
| |
| /* No found */ |
| errno = ENOENT; |
| return NULL; |
| } |
| |
| /* |
| * selabel_stats() handler |
| */ |
| static void |
| db_stats(struct selabel_handle *rec) |
| { |
| catalog_t *catalog = (catalog_t *)rec->data; |
| unsigned int i, total = 0; |
| |
| for (i = 0; i < catalog->nspec; i++) |
| total += catalog->specs[i].matches; |
| |
| selinux_log(SELINUX_INFO, "%u entries, %u matches made\n", |
| catalog->nspec, total); |
| } |
| |
| /* |
| * selabel_open() handler |
| */ |
| static catalog_t * |
| db_init(const struct selinux_opt *opts, unsigned nopts, |
| struct selabel_handle *rec) |
| { |
| catalog_t *catalog; |
| FILE *filp; |
| const char *path = NULL; |
| char *line_buf = NULL; |
| size_t line_len = 0; |
| unsigned int line_num = 0; |
| unsigned int i; |
| struct stat sb; |
| |
| /* |
| * Initialize catalog data structure |
| */ |
| catalog = malloc(sizeof(catalog_t) + 32 * sizeof(spec_t)); |
| if (!catalog) |
| return NULL; |
| catalog->limit = 32; |
| catalog->nspec = 0; |
| |
| /* |
| * Process arguments |
| * |
| * SELABEL_OPT_PATH: |
| * It allows to specify an alternative specification file instead of |
| * the default one. If RDBMS is not SE-PostgreSQL, it may need to |
| * specify an explicit specfile for database objects. |
| */ |
| while (nopts--) { |
| switch (opts[nopts].type) { |
| case SELABEL_OPT_PATH: |
| path = opts[nopts].value; |
| break; |
| } |
| } |
| |
| /* |
| * Open the specification file |
| */ |
| if (!path) |
| path = selinux_sepgsql_context_path(); |
| |
| if ((filp = fopen(path, "rb")) == NULL) { |
| free(catalog); |
| return NULL; |
| } |
| if (fstat(fileno(filp), &sb) < 0) |
| return NULL; |
| if (!S_ISREG(sb.st_mode)) { |
| errno = EINVAL; |
| return NULL; |
| } |
| rec->spec_file = strdup(path); |
| |
| /* |
| * Parse for each lines |
| */ |
| while (getline(&line_buf, &line_len, filp) > 0) { |
| /* |
| * Expand catalog array, if necessary |
| */ |
| if (catalog->limit == catalog->nspec) { |
| size_t length; |
| unsigned int new_limit = 2 * catalog->limit; |
| catalog_t *new_catalog; |
| |
| length = sizeof(catalog_t) |
| + new_limit * sizeof(spec_t); |
| new_catalog = realloc(catalog, length); |
| if (!new_catalog) |
| goto out_error; |
| |
| catalog = new_catalog; |
| catalog->limit = new_limit; |
| } |
| |
| /* |
| * Parse a line |
| */ |
| if (process_line(path, line_buf, ++line_num, catalog) < 0) |
| goto out_error; |
| } |
| free(line_buf); |
| |
| if (digest_add_specfile(rec->digest, filp, NULL, sb.st_size, path) < 0) |
| goto out_error; |
| |
| digest_gen_hash(rec->digest); |
| |
| fclose(filp); |
| |
| return catalog; |
| |
| out_error: |
| for (i = 0; i < catalog->nspec; i++) { |
| spec_t *spec = &catalog->specs[i]; |
| |
| free(spec->key); |
| free(spec->lr.ctx_raw); |
| free(spec->lr.ctx_trans); |
| } |
| free(catalog); |
| |
| return NULL; |
| } |
| |
| /* |
| * Initialize selabel_handle and load the entries of specfile |
| */ |
| int selabel_db_init(struct selabel_handle *rec, |
| const struct selinux_opt *opts, unsigned nopts) |
| { |
| rec->func_close = &db_close; |
| rec->func_lookup = &db_lookup; |
| rec->func_stats = &db_stats; |
| rec->data = db_init(opts, nopts, rec); |
| |
| return !rec->data ? -1 : 0; |
| } |