| #include <errno.h> |
| #include <stdio.h> |
| #include <arpa/inet.h> |
| #include <sys/types.h> |
| #include <dirent.h> |
| #include <sys/stat.h> |
| #include <stdlib.h> |
| #include <sys/mman.h> |
| #include <fcntl.h> |
| #include <stdbool.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <limits.h> |
| |
| #include <arpa/inet.h> /* ntohl */ |
| |
| #include "reglib.h" |
| #include "regdb.h" |
| |
| #ifdef USE_OPENSSL |
| #include <openssl/objects.h> |
| #include <openssl/rsa.h> |
| #include <openssl/sha.h> |
| #include <openssl/pem.h> |
| #endif |
| |
| #ifdef USE_GCRYPT |
| #include <gcrypt.h> |
| #endif |
| |
| #include "reglib.h" |
| |
| #ifdef USE_OPENSSL |
| #include "keys-ssl.c" |
| #endif |
| |
| #ifdef USE_GCRYPT |
| #include "keys-gcrypt.c" |
| #endif |
| |
| int debug = 0; |
| |
| struct reglib_rule_parse_list { |
| int n_parsers; |
| int (*rule_parsers[])(char *line, struct ieee80211_reg_rule *reg_rule); |
| }; |
| |
| struct reglib_country_parse_list { |
| int n_parsers; |
| int (*country_parsers[])(char *line, struct ieee80211_regdomain *rd); |
| }; |
| |
| |
| void * |
| reglib_get_file_ptr(uint8_t *db, size_t dblen, size_t structlen, uint32_t ptr) |
| { |
| uint32_t p = ntohl(ptr); |
| |
| if (structlen > dblen) { |
| fprintf(stderr, "Invalid database file, too short!\n"); |
| exit(3); |
| } |
| |
| if (p > dblen - structlen) { |
| fprintf(stderr, "Invalid database file, bad pointer!\n"); |
| exit(3); |
| } |
| |
| return (void *)(db + p); |
| } |
| |
| static size_t |
| reglib_array_len(size_t baselen, unsigned int elemcount, size_t elemlen) |
| { |
| if (elemcount > (SIZE_MAX - baselen) / elemlen) { |
| fprintf(stderr, "Invalid database file, count too large!\n"); |
| exit(3); |
| } |
| |
| return baselen + elemcount * elemlen; |
| } |
| |
| /* |
| * reglib_verify_db_signature(): |
| * |
| * Checks the validity of the signature found on the regulatory |
| * database against the array 'keys'. Returns 1 if there exists |
| * at least one key in the array such that the signature is valid |
| * against that key; 0 otherwise. |
| */ |
| |
| #ifdef USE_OPENSSL |
| int reglib_verify_db_signature(uint8_t *db, size_t dblen, size_t siglen) |
| { |
| RSA *rsa; |
| uint8_t hash[SHA_DIGEST_LENGTH]; |
| unsigned int i; |
| int ok = 0; |
| DIR *pubkey_dir; |
| struct dirent *nextfile; |
| FILE *keyfile; |
| char filename[PATH_MAX]; |
| |
| if (SHA1(db, dblen, hash) != hash) { |
| fprintf(stderr, "Failed to calculate SHA1 sum.\n"); |
| goto out; |
| } |
| |
| for (i = 0; (i < sizeof(keys)/sizeof(keys[0])) && (!ok); i++) { |
| rsa = RSA_new(); |
| if (!rsa) { |
| fprintf(stderr, "Failed to create RSA key.\n"); |
| goto out; |
| } |
| |
| rsa->e = &keys[i].e; |
| rsa->n = &keys[i].n; |
| |
| ok = RSA_verify(NID_sha1, hash, SHA_DIGEST_LENGTH, |
| db + dblen, siglen, rsa) == 1; |
| |
| rsa->e = NULL; |
| rsa->n = NULL; |
| RSA_free(rsa); |
| } |
| if (!ok && (pubkey_dir = opendir(PUBKEY_DIR))) { |
| while (!ok && (nextfile = readdir(pubkey_dir))) { |
| snprintf(filename, PATH_MAX, "%s/%s", PUBKEY_DIR, |
| nextfile->d_name); |
| if ((keyfile = fopen(filename, "rb"))) { |
| rsa = PEM_read_RSA_PUBKEY(keyfile, |
| NULL, NULL, NULL); |
| if (rsa) |
| ok = RSA_verify(NID_sha1, hash, SHA_DIGEST_LENGTH, |
| db + dblen, siglen, rsa) == 1; |
| RSA_free(rsa); |
| fclose(keyfile); |
| } |
| } |
| closedir(pubkey_dir); |
| } |
| |
| if (!ok) |
| fprintf(stderr, "Database signature verification failed.\n"); |
| |
| out: |
| return ok; |
| } |
| #endif /* USE_OPENSSL */ |
| |
| #ifdef USE_GCRYPT |
| int reglib_verify_db_signature(uint8_t *db, size_t dblen, size_t siglen) |
| { |
| gcry_mpi_t mpi_e, mpi_n; |
| gcry_sexp_t rsa, signature, data; |
| uint8_t hash[20]; |
| unsigned int i; |
| int ok = 0; |
| |
| /* initialise */ |
| gcry_check_version(NULL); |
| |
| /* hash the db */ |
| gcry_md_hash_buffer(GCRY_MD_SHA1, hash, db, dblen); |
| |
| if (gcry_sexp_build(&data, NULL, "(data (flags pkcs1) (hash sha1 %b))", |
| 20, hash)) { |
| fprintf(stderr, "Failed to build data S-expression.\n"); |
| return ok; |
| } |
| |
| if (gcry_sexp_build(&signature, NULL, "(sig-val (rsa (s %b)))", |
| siglen, db + dblen)) { |
| fprintf(stderr, "Failed to build signature S-expression.\n"); |
| gcry_sexp_release(data); |
| return ok; |
| } |
| |
| for (i = 0; (i < sizeof(keys)/sizeof(keys[0])) && (!ok); i++) { |
| if (gcry_mpi_scan(&mpi_e, GCRYMPI_FMT_USG, |
| keys[i].e, keys[i].len_e, NULL) || |
| gcry_mpi_scan(&mpi_n, GCRYMPI_FMT_USG, |
| keys[i].n, keys[i].len_n, NULL)) { |
| fprintf(stderr, "Failed to convert numbers.\n"); |
| goto out; |
| } |
| |
| if (gcry_sexp_build(&rsa, NULL, |
| "(public-key (rsa (n %m) (e %m)))", |
| mpi_n, mpi_e)) { |
| fprintf(stderr, "Failed to build RSA S-expression.\n"); |
| gcry_mpi_release(mpi_e); |
| gcry_mpi_release(mpi_n); |
| goto out; |
| } |
| |
| ok = gcry_pk_verify(signature, data, rsa) == 0; |
| gcry_mpi_release(mpi_e); |
| gcry_mpi_release(mpi_n); |
| gcry_sexp_release(rsa); |
| } |
| |
| if (!ok) |
| fprintf(stderr, "Database signature verification failed.\n"); |
| |
| out: |
| gcry_sexp_release(data); |
| gcry_sexp_release(signature); |
| return ok; |
| } |
| #endif /* USE_GCRYPT */ |
| |
| #if !defined(USE_OPENSSL) && !defined(USE_GCRYPT) |
| int reglib_verify_db_signature(uint8_t *db, size_t dblen, size_t siglen) |
| { |
| return 1; |
| } |
| #endif |
| |
| const struct reglib_regdb_ctx *reglib_malloc_regdb_ctx(const char *regdb_file) |
| { |
| struct regdb_file_header *header; |
| struct reglib_regdb_ctx *ctx; |
| |
| ctx = malloc(sizeof(struct reglib_regdb_ctx)); |
| if (!ctx) |
| return NULL; |
| |
| memset(ctx, 0, sizeof(struct reglib_regdb_ctx)); |
| |
| ctx->fd = open(regdb_file, O_RDONLY); |
| |
| if (ctx->fd < 0) { |
| free(ctx); |
| return NULL; |
| } |
| |
| if (fstat(ctx->fd, &ctx->stat)) { |
| close(ctx->fd); |
| free(ctx); |
| return NULL; |
| } |
| |
| ctx->real_dblen = ctx->stat.st_size; |
| |
| ctx->db = mmap(NULL, ctx->real_dblen, PROT_READ, |
| MAP_PRIVATE, ctx->fd, 0); |
| if (ctx->db == MAP_FAILED) { |
| close(ctx->fd); |
| free(ctx); |
| return NULL; |
| } |
| |
| ctx->header = reglib_get_file_ptr(ctx->db, ctx->real_dblen, |
| sizeof(struct regdb_file_header), |
| 0); |
| header = ctx->header; |
| |
| if (ntohl(header->magic) != REGDB_MAGIC) |
| goto err_out; |
| |
| if (ntohl(header->version) != REGDB_VERSION) |
| goto err_out; |
| |
| ctx->siglen = ntohl(header->signature_length); |
| |
| if (ctx->siglen > ctx->real_dblen - sizeof(*header)) |
| goto err_out; |
| |
| /* The actual dblen does not take into account the signature */ |
| ctx->dblen = ctx->real_dblen - ctx->siglen; |
| |
| /* verify signature */ |
| if (!reglib_verify_db_signature(ctx->db, ctx->dblen, ctx->siglen)) |
| goto err_out; |
| |
| ctx->verified = true; |
| ctx->num_countries = ntohl(header->reg_country_num); |
| ctx->countries = reglib_get_file_ptr(ctx->db, |
| ctx->dblen, |
| sizeof(struct regdb_file_reg_country) * ctx->num_countries, |
| header->reg_country_ptr); |
| return ctx; |
| |
| err_out: |
| close(ctx->fd); |
| munmap(ctx->db, ctx->real_dblen); |
| free(ctx); |
| return NULL; |
| } |
| |
| void reglib_free_regdb_ctx(const struct reglib_regdb_ctx *regdb_ctx) |
| { |
| struct reglib_regdb_ctx *ctx; |
| |
| if (!regdb_ctx) |
| return; |
| |
| ctx = (struct reglib_regdb_ctx *) regdb_ctx; |
| |
| memset(ctx, 0, sizeof(struct reglib_regdb_ctx)); |
| close(ctx->fd); |
| munmap(ctx->db, ctx->real_dblen); |
| free(ctx); |
| } |
| |
| static void reg_rule2rd(uint8_t *db, size_t dblen, |
| uint32_t ruleptr, struct ieee80211_reg_rule *rd_reg_rule) |
| { |
| struct regdb_file_reg_rule *rule; |
| struct regdb_file_freq_range *freq; |
| struct regdb_file_power_rule *power; |
| |
| struct ieee80211_freq_range *rd_freq_range = &rd_reg_rule->freq_range; |
| struct ieee80211_power_rule *rd_power_rule = &rd_reg_rule->power_rule; |
| |
| rule = reglib_get_file_ptr(db, dblen, sizeof(*rule), ruleptr); |
| freq = reglib_get_file_ptr(db, dblen, sizeof(*freq), rule->freq_range_ptr); |
| power = reglib_get_file_ptr(db, dblen, sizeof(*power), rule->power_rule_ptr); |
| |
| rd_freq_range->start_freq_khz = ntohl(freq->start_freq); |
| rd_freq_range->end_freq_khz = ntohl(freq->end_freq); |
| rd_freq_range->max_bandwidth_khz = ntohl(freq->max_bandwidth); |
| |
| rd_power_rule->max_antenna_gain = ntohl(power->max_antenna_gain); |
| rd_power_rule->max_eirp = ntohl(power->max_eirp); |
| |
| rd_reg_rule->flags = ntohl(rule->flags); |
| |
| if (rd_reg_rule->flags & RRF_NO_IR_ALL) |
| rd_reg_rule->flags |= RRF_NO_IR_ALL; |
| } |
| |
| /* Converts a file regdomain to ieee80211_regdomain, easier to manage */ |
| const static struct ieee80211_regdomain * |
| country2rd(const struct reglib_regdb_ctx *ctx, |
| struct regdb_file_reg_country *country) |
| { |
| struct regdb_file_reg_rules_collection *rcoll; |
| struct ieee80211_regdomain *rd; |
| unsigned int i, num_rules; |
| size_t size_of_rd; |
| |
| rcoll = reglib_get_file_ptr(ctx->db, ctx->dblen, sizeof(*rcoll), |
| country->reg_collection_ptr); |
| num_rules = ntohl(rcoll->reg_rule_num); |
| /* re-get pointer with sanity checking for num_rules */ |
| rcoll = reglib_get_file_ptr(ctx->db, ctx->dblen, |
| reglib_array_len(sizeof(*rcoll), num_rules, |
| sizeof(uint32_t)), |
| country->reg_collection_ptr); |
| |
| size_of_rd = reglib_array_len(sizeof(struct ieee80211_regdomain), |
| num_rules, |
| sizeof(struct ieee80211_reg_rule)); |
| |
| rd = malloc(size_of_rd); |
| if (!rd) |
| return NULL; |
| |
| memset(rd, 0, size_of_rd); |
| |
| rd->alpha2[0] = country->alpha2[0]; |
| rd->alpha2[1] = country->alpha2[1]; |
| rd->dfs_region = country->creqs & 0x3; |
| rd->n_reg_rules = num_rules; |
| |
| for (i = 0; i < num_rules; i++) { |
| reg_rule2rd(ctx->db, ctx->dblen, rcoll->reg_rule_ptrs[i], |
| &rd->reg_rules[i]); |
| } |
| |
| return rd; |
| } |
| |
| const struct ieee80211_regdomain * |
| reglib_get_rd_idx(unsigned int idx, const struct reglib_regdb_ctx *ctx) |
| { |
| struct regdb_file_reg_country *country; |
| |
| if (!ctx) |
| return NULL; |
| |
| if (idx >= ctx->num_countries) |
| return NULL; |
| |
| country = ctx->countries + idx; |
| |
| return country2rd(ctx, country); |
| } |
| |
| const struct ieee80211_regdomain * |
| reglib_get_rd_alpha2(const char *alpha2, const char *file) |
| { |
| const struct reglib_regdb_ctx *ctx; |
| const struct ieee80211_regdomain *rd = NULL; |
| struct regdb_file_reg_country *country; |
| bool found_country = false; |
| unsigned int i; |
| |
| ctx = reglib_malloc_regdb_ctx(file); |
| if (!ctx) |
| return NULL; |
| |
| for (i = 0; i < ctx->num_countries; i++) { |
| country = ctx->countries + i; |
| if (memcmp(country->alpha2, alpha2, 2) == 0) { |
| found_country = 1; |
| break; |
| } |
| } |
| |
| if (!found_country) |
| goto out; |
| |
| rd = country2rd(ctx, country); |
| if (!rd) |
| goto out; |
| |
| out: |
| reglib_free_regdb_ctx(ctx); |
| return rd; |
| } |
| |
| /* Sanity check on a regulatory rule */ |
| static int is_valid_reg_rule(const struct ieee80211_reg_rule *rule) |
| { |
| const struct ieee80211_freq_range *freq_range = &rule->freq_range; |
| uint32_t freq_diff; |
| |
| if (freq_range->start_freq_khz == 0 || freq_range->end_freq_khz == 0) |
| return 0; |
| |
| if (freq_range->start_freq_khz > freq_range->end_freq_khz) |
| return 0; |
| |
| freq_diff = freq_range->end_freq_khz - freq_range->start_freq_khz; |
| |
| if (freq_range->end_freq_khz <= freq_range->start_freq_khz || |
| freq_range->max_bandwidth_khz > freq_diff) |
| return 0; |
| |
| return 1; |
| } |
| |
| int reglib_is_valid_rd(const struct ieee80211_regdomain *rd) |
| { |
| const struct ieee80211_reg_rule *reg_rule = NULL; |
| unsigned int i; |
| |
| if (!rd->n_reg_rules) |
| return 0; |
| |
| for (i = 0; i < rd->n_reg_rules; i++) { |
| reg_rule = &rd->reg_rules[i]; |
| if (!is_valid_reg_rule(reg_rule)) |
| return 0; |
| } |
| return 1; |
| } |
| |
| /* |
| * Helper for reglib_intersect_rds(), this does the real |
| * mathematical intersection fun |
| */ |
| static int reg_rules_intersect(const struct ieee80211_reg_rule *rule1, |
| const struct ieee80211_reg_rule *rule2, |
| struct ieee80211_reg_rule *intersected_rule) |
| { |
| const struct ieee80211_freq_range *freq_range1, *freq_range2; |
| struct ieee80211_freq_range *freq_range; |
| const struct ieee80211_power_rule *power_rule1, *power_rule2; |
| struct ieee80211_power_rule *power_rule; |
| uint32_t freq_diff; |
| |
| freq_range1 = &rule1->freq_range; |
| freq_range2 = &rule2->freq_range; |
| freq_range = &intersected_rule->freq_range; |
| |
| power_rule1 = &rule1->power_rule; |
| power_rule2 = &rule2->power_rule; |
| power_rule = &intersected_rule->power_rule; |
| |
| freq_range->start_freq_khz = reglib_max(freq_range1->start_freq_khz, |
| freq_range2->start_freq_khz); |
| freq_range->end_freq_khz = reglib_min(freq_range1->end_freq_khz, |
| freq_range2->end_freq_khz); |
| freq_range->max_bandwidth_khz = reglib_min(freq_range1->max_bandwidth_khz, |
| freq_range2->max_bandwidth_khz); |
| |
| freq_diff = freq_range->end_freq_khz - freq_range->start_freq_khz; |
| if (freq_range->max_bandwidth_khz > freq_diff) |
| freq_range->max_bandwidth_khz = freq_diff; |
| |
| power_rule->max_eirp = reglib_min(power_rule1->max_eirp, |
| power_rule2->max_eirp); |
| power_rule->max_antenna_gain = reglib_min(power_rule1->max_antenna_gain, |
| power_rule2->max_antenna_gain); |
| |
| intersected_rule->flags = rule1->flags | rule2->flags; |
| |
| if (!is_valid_reg_rule(intersected_rule)) |
| return -EINVAL; |
| |
| return 0; |
| } |
| |
| /** |
| * reglib_intersect_rds - do the intersection between two regulatory domains |
| * @rd1: first regulatory domain |
| * @rd2: second regulatory domain |
| * |
| * Use this function to get the intersection between two regulatory domains. |
| * Once completed we will mark the alpha2 for the rd as intersected, "98", |
| * as no one single alpha2 can represent this regulatory domain. |
| * |
| * Returns a pointer to the regulatory domain structure which will hold the |
| * resulting intersection of rules between rd1 and rd2. We will |
| * malloc() this structure for you. |
| */ |
| struct ieee80211_regdomain * |
| reglib_intersect_rds(const struct ieee80211_regdomain *rd1, |
| const struct ieee80211_regdomain *rd2) |
| { |
| int r; |
| size_t size_of_regd; |
| unsigned int x, y; |
| unsigned int num_rules = 0, rule_idx = 0; |
| const struct ieee80211_reg_rule *rule1, *rule2; |
| struct ieee80211_reg_rule *intersected_rule; |
| struct ieee80211_regdomain *rd; |
| /* This is just a dummy holder to help us count */ |
| struct ieee80211_reg_rule irule; |
| |
| /* Uses the stack temporarily for counter arithmetic */ |
| intersected_rule = &irule; |
| |
| memset(intersected_rule, 0, sizeof(struct ieee80211_reg_rule)); |
| |
| if (!rd1 || !rd2) |
| return NULL; |
| |
| /* First we get a count of the rules we'll need, then we actually |
| * build them. This is to so we can malloc() and free() a |
| * regdomain once. The reason we use reg_rules_intersect() here |
| * is it will return -EINVAL if the rule computed makes no sense. |
| * All rules that do check out OK are valid. */ |
| |
| for (x = 0; x < rd1->n_reg_rules; x++) { |
| rule1 = &rd1->reg_rules[x]; |
| for (y = 0; y < rd2->n_reg_rules; y++) { |
| rule2 = &rd2->reg_rules[y]; |
| if (!reg_rules_intersect(rule1, rule2, |
| intersected_rule)) |
| num_rules++; |
| memset(intersected_rule, 0, |
| sizeof(struct ieee80211_reg_rule)); |
| } |
| } |
| |
| if (!num_rules) |
| return NULL; |
| |
| size_of_regd = reglib_array_len(sizeof(struct ieee80211_regdomain), |
| num_rules + 1, |
| sizeof(struct ieee80211_reg_rule)); |
| |
| rd = malloc(size_of_regd); |
| if (!rd) |
| return NULL; |
| |
| memset(rd, 0, size_of_regd); |
| |
| for (x = 0; x < rd1->n_reg_rules; x++) { |
| rule1 = &rd1->reg_rules[x]; |
| for (y = 0; y < rd2->n_reg_rules; y++) { |
| rule2 = &rd2->reg_rules[y]; |
| /* This time around instead of using the stack lets |
| * write to the target rule directly saving ourselves |
| * a memcpy() */ |
| intersected_rule = &rd->reg_rules[rule_idx]; |
| r = reg_rules_intersect(rule1, rule2, |
| intersected_rule); |
| if (r) |
| continue; |
| rule_idx++; |
| } |
| } |
| |
| if (rule_idx != num_rules) { |
| free(rd); |
| return NULL; |
| } |
| |
| rd->n_reg_rules = num_rules; |
| rd->alpha2[0] = '9'; |
| rd->alpha2[1] = '9'; |
| |
| return rd; |
| } |
| |
| const struct ieee80211_regdomain * |
| reglib_intersect_regdb(const struct reglib_regdb_ctx *ctx) |
| { |
| const struct ieee80211_regdomain *rd; |
| struct ieee80211_regdomain *prev_rd_intsct = NULL, *rd_intsct = NULL; |
| int intersected = 0; |
| unsigned int idx = 0; |
| |
| if (!ctx) |
| return NULL; |
| |
| reglib_for_each_country(rd, idx, ctx) { |
| if (reglib_is_world_regdom((const char *) rd->alpha2)) { |
| free((struct ieee80211_regdomain *) rd); |
| continue; |
| } |
| |
| if (!prev_rd_intsct) { |
| prev_rd_intsct = (struct ieee80211_regdomain *) rd; |
| continue; |
| } |
| |
| if (rd_intsct) { |
| free(prev_rd_intsct); |
| prev_rd_intsct = (struct ieee80211_regdomain *) rd_intsct; |
| } |
| |
| rd_intsct = reglib_intersect_rds(prev_rd_intsct, rd); |
| if (!rd_intsct) { |
| free(prev_rd_intsct); |
| free((struct ieee80211_regdomain *) rd); |
| return NULL; |
| } |
| |
| intersected++; |
| free((struct ieee80211_regdomain *) rd); |
| } |
| |
| if (!idx) |
| return NULL; |
| |
| if (intersected <= 0) { |
| rd_intsct = prev_rd_intsct; |
| prev_rd_intsct = NULL; |
| if (idx > 1) { |
| free(rd_intsct); |
| return NULL; |
| } |
| } |
| |
| if (prev_rd_intsct) |
| free(prev_rd_intsct); |
| |
| return rd_intsct; |
| } |
| |
| static const char *dfs_domain_name(enum regdb_dfs_regions region) |
| { |
| switch (region) { |
| case REGDB_DFS_UNSET: |
| return "DFS-UNSET"; |
| case REGDB_DFS_FCC: |
| return "DFS-FCC"; |
| case REGDB_DFS_ETSI: |
| return "DFS-ETSI"; |
| case REGDB_DFS_JP: |
| return "DFS-JP"; |
| default: |
| return "DFS-invalid"; |
| } |
| } |
| |
| static void print_reg_rule(const struct ieee80211_reg_rule *rule) |
| { |
| const struct ieee80211_freq_range *freq; |
| const struct ieee80211_power_rule *power; |
| |
| freq = &rule->freq_range; |
| power = &rule->power_rule; |
| |
| printf("\t(%.3f - %.3f @ %.3f), ", |
| ((float)(freq->start_freq_khz))/1000.0, |
| ((float)(freq->end_freq_khz))/1000.0, |
| ((float)(freq->max_bandwidth_khz))/1000.0); |
| |
| printf("("); |
| |
| if (power->max_eirp) |
| printf("%.2f)", ((float)(power->max_eirp)/100.0)); |
| else |
| printf("N/A)"); |
| |
| if (rule->flags & RRF_NO_OFDM) |
| printf(", NO-OFDM"); |
| if (rule->flags & RRF_NO_CCK) |
| printf(", NO-CCK"); |
| if (rule->flags & RRF_NO_INDOOR) |
| printf(", NO-INDOOR"); |
| if (rule->flags & RRF_NO_OUTDOOR) |
| printf(", NO-OUTDOOR"); |
| if (rule->flags & RRF_DFS) |
| printf(", DFS"); |
| if (rule->flags & RRF_PTP_ONLY) |
| printf(", PTP-ONLY"); |
| if (rule->flags & RRF_PTMP_ONLY) |
| printf(", PTMP-ONLY"); |
| if (rule->flags & RRF_NO_IR_ALL) |
| printf(", NO-IR"); |
| |
| printf("\n"); |
| } |
| |
| void reglib_print_regdom(const struct ieee80211_regdomain *rd) |
| { |
| unsigned int i; |
| printf("country %.2s: %s\n", rd->alpha2, |
| dfs_domain_name(rd->dfs_region)); |
| for (i = 0; i < rd->n_reg_rules; i++) |
| print_reg_rule(&rd->reg_rules[i]); |
| printf("\n"); |
| } |
| |
| static unsigned int reglib_parse_dfs_region(char *dfs_region) |
| { |
| if (strncmp(dfs_region, "DFS-FCC", 7) == 0) |
| return REGDB_DFS_FCC; |
| if (strncmp(dfs_region, "DFS-ETSI", 8) == 0) |
| return REGDB_DFS_ETSI; |
| if (strncmp(dfs_region, "DFS-JP", 6) == 0) |
| return REGDB_DFS_JP; |
| return REGDB_DFS_UNSET; |
| } |
| |
| static uint32_t reglib_parse_rule_flag(char *flag_s) |
| { |
| if (strncmp(flag_s, "NO-OFDM", 7) == 0) |
| return RRF_NO_OFDM; |
| if (strncmp(flag_s, "NO-CCK", 6) == 0) |
| return RRF_NO_CCK; |
| if (strncmp(flag_s, "NO-INDOOR", 9) == 0) |
| return RRF_NO_INDOOR; |
| if (strncmp(flag_s, "NO-OUTDOOR", 10) == 0) |
| return RRF_NO_OUTDOOR; |
| if (strncmp(flag_s, "DFS", 3) == 0) |
| return RRF_DFS; |
| if (strncmp(flag_s, "PTP-ONLY", 8) == 0) |
| return RRF_PTP_ONLY; |
| if (strncmp(flag_s, "PTMP-ONLY", 9) == 0) |
| return RRF_PTMP_ONLY; |
| if (strncmp(flag_s, "NO-IR", 5) == 0) |
| return RRF_NO_IR; |
| |
| return 0; |
| } |
| |
| static int |
| reglib_parse_rule_simple(char *line, struct ieee80211_reg_rule *reg_rule) |
| { |
| int hits; |
| float start_freq_khz, end_freq_khz, max_bandwidth_khz, max_eirp; |
| |
| hits = sscanf(line, "\t(%f - %f @ %f), (%f)\n", |
| &start_freq_khz, |
| &end_freq_khz, |
| &max_bandwidth_khz, |
| &max_eirp); |
| |
| if (hits != 4) |
| return -EINVAL; |
| |
| reg_rule->freq_range.start_freq_khz = |
| REGLIB_MHZ_TO_KHZ(start_freq_khz); |
| reg_rule->freq_range.end_freq_khz = |
| REGLIB_MHZ_TO_KHZ(end_freq_khz); |
| reg_rule->freq_range.max_bandwidth_khz = |
| REGLIB_MHZ_TO_KHZ(max_bandwidth_khz); |
| reg_rule->power_rule.max_eirp = |
| REGLIB_DBM_TO_MBM(max_eirp); |
| |
| reg_rule->flags = 0; |
| |
| if (debug) |
| printf("reglib_parse_rule_simple(): %d line: %s", hits, line); |
| |
| |
| return 0; |
| } |
| |
| static int |
| reglib_parse_rule_simple_mw(char *line, struct ieee80211_reg_rule *reg_rule) |
| { |
| int hits; |
| float start_freq_khz, end_freq_khz, max_bandwidth_khz, max_eirp; |
| char mw[3]; |
| |
| hits = sscanf(line, "\t(%f - %f @ %f), (%f %2[mW])\n", |
| &start_freq_khz, |
| &end_freq_khz, |
| &max_bandwidth_khz, |
| &max_eirp, mw); |
| |
| if (hits != 4) |
| return -EINVAL; |
| |
| |
| reg_rule->freq_range.start_freq_khz = |
| REGLIB_MHZ_TO_KHZ(start_freq_khz); |
| reg_rule->freq_range.end_freq_khz = |
| REGLIB_MHZ_TO_KHZ(end_freq_khz); |
| reg_rule->freq_range.max_bandwidth_khz = |
| REGLIB_MHZ_TO_KHZ(max_bandwidth_khz); |
| reg_rule->power_rule.max_eirp = |
| REGLIB_MW_TO_MBM(max_eirp); |
| |
| reg_rule->flags = 0; |
| |
| if (debug) |
| printf("reglib_parse_rule_simple_mw(): %d line: %s", |
| hits, line); |
| |
| return 0; |
| } |
| |
| static int |
| reglib_parse_rule_args(char *line, struct ieee80211_reg_rule *reg_rule) |
| { |
| #define IGNORE_COMMA_OR_SPACE "%*[ ,]" |
| int hits; |
| char flag_list[9][100]; |
| unsigned int i = 0; |
| float start_freq_khz, end_freq_khz, max_bandwidth_khz, max_eirp; |
| |
| for (i = 0; i < 9; i++) |
| memset(flag_list[i], 0, sizeof(flag_list[i])); |
| |
| hits = sscanf(line, "\t(%f - %f @ %f), (%f)" |
| IGNORE_COMMA_OR_SPACE "%s" |
| IGNORE_COMMA_OR_SPACE "%s" |
| IGNORE_COMMA_OR_SPACE "%s" |
| IGNORE_COMMA_OR_SPACE "%s" |
| IGNORE_COMMA_OR_SPACE "%s" |
| IGNORE_COMMA_OR_SPACE "%s" |
| IGNORE_COMMA_OR_SPACE "%s" |
| IGNORE_COMMA_OR_SPACE "%s" |
| IGNORE_COMMA_OR_SPACE "%s", |
| &start_freq_khz, |
| &end_freq_khz, |
| &max_bandwidth_khz, |
| &max_eirp, |
| flag_list[0], |
| flag_list[1], |
| flag_list[2], |
| flag_list[3], |
| flag_list[4], |
| flag_list[5], |
| flag_list[6], |
| flag_list[7], |
| flag_list[8]); |
| |
| if (hits < 5) |
| return -EINVAL; |
| |
| reg_rule->freq_range.start_freq_khz = |
| REGLIB_MHZ_TO_KHZ(start_freq_khz); |
| reg_rule->freq_range.end_freq_khz = |
| REGLIB_MHZ_TO_KHZ(end_freq_khz); |
| reg_rule->freq_range.max_bandwidth_khz = |
| REGLIB_MHZ_TO_KHZ(max_bandwidth_khz); |
| reg_rule->power_rule.max_eirp = |
| REGLIB_DBM_TO_MBM(max_eirp); |
| |
| for (i = 0; i < 8; i++) |
| reg_rule->flags |= reglib_parse_rule_flag(flag_list[i]); |
| |
| if (debug) |
| printf("reglib_parse_rule_args(): %d flags: %d, line: %s", |
| hits, reg_rule->flags, line); |
| |
| return 0; |
| #undef IGNORE_COMMA_OR_SPACE |
| } |
| |
| |
| static int |
| reglib_parse_rule_args_mw(char *line, struct ieee80211_reg_rule *reg_rule) |
| { |
| #define IGNORE_COMMA_OR_SPACE "%*[ ,]" |
| int hits; |
| char flag_list[9][100]; |
| unsigned int i = 0; |
| char mw[3]; |
| float start_freq_khz, end_freq_khz, max_bandwidth_khz, max_eirp; |
| |
| for (i = 0; i < 9; i++) |
| memset(flag_list[i], 0, sizeof(flag_list[i])); |
| |
| hits = sscanf(line, "\t(%f - %f @ %f), (%f %2[mW])" |
| IGNORE_COMMA_OR_SPACE "%s" |
| IGNORE_COMMA_OR_SPACE "%s" |
| IGNORE_COMMA_OR_SPACE "%s" |
| IGNORE_COMMA_OR_SPACE "%s" |
| IGNORE_COMMA_OR_SPACE "%s" |
| IGNORE_COMMA_OR_SPACE "%s" |
| IGNORE_COMMA_OR_SPACE "%s" |
| IGNORE_COMMA_OR_SPACE "%s" |
| IGNORE_COMMA_OR_SPACE "%s", |
| &start_freq_khz, |
| &end_freq_khz, |
| &max_bandwidth_khz, |
| &max_eirp, |
| mw, |
| flag_list[0], |
| flag_list[1], |
| flag_list[2], |
| flag_list[3], |
| flag_list[4], |
| flag_list[5], |
| flag_list[6], |
| flag_list[7], |
| flag_list[8]); |
| |
| if (hits < 5) |
| return -EINVAL; |
| |
| reg_rule->freq_range.start_freq_khz = |
| REGLIB_MHZ_TO_KHZ(start_freq_khz); |
| reg_rule->freq_range.end_freq_khz = |
| REGLIB_MHZ_TO_KHZ(end_freq_khz); |
| reg_rule->freq_range.max_bandwidth_khz = |
| REGLIB_MHZ_TO_KHZ(max_bandwidth_khz); |
| reg_rule->power_rule.max_eirp = |
| REGLIB_MW_TO_MBM(max_eirp); |
| |
| for (i = 0; i < 8; i++) |
| reg_rule->flags |= reglib_parse_rule_flag(flag_list[i]); |
| |
| if (debug) |
| printf("reglib_parse_rule_args_mw(): %d flags: %d, line: %s", |
| hits, reg_rule->flags, line); |
| return 0; |
| #undef IGNORE_COMMA_OR_SPACE |
| } |
| |
| static int reglib_parse_rule(FILE *fp, struct ieee80211_reg_rule *reg_rule) |
| { |
| char line[1024]; |
| char *line_p; |
| unsigned int i; |
| int r = 0; |
| struct reglib_rule_parse_list *reglib_rule_parsers; |
| size_t size_parsers = sizeof(struct reglib_rule_parse_list) + |
| 4 * sizeof(int (*)(char *, struct ieee80211_reg_rule *)); |
| |
| reglib_rule_parsers = malloc(size_parsers); |
| if (!reglib_rule_parsers) |
| return -EINVAL; |
| memset(reglib_rule_parsers, 0, size_parsers); |
| |
| reglib_rule_parsers->n_parsers = 4; |
| |
| /* |
| * XXX: sscanf() is a bit odd with picking up mW |
| * case over the simple one, this order however works, |
| * gotta figure out how to be more precise. |
| */ |
| reglib_rule_parsers->rule_parsers[0] = reglib_parse_rule_args_mw; |
| reglib_rule_parsers->rule_parsers[1] = reglib_parse_rule_args; |
| reglib_rule_parsers->rule_parsers[2] = reglib_parse_rule_simple; |
| reglib_rule_parsers->rule_parsers[3] = reglib_parse_rule_simple_mw; |
| |
| memset(line, 0, sizeof(line)); |
| line_p = fgets(line, sizeof(line), fp); |
| if (line_p != line) |
| return -EINVAL; |
| |
| for (i = 0; i < reglib_rule_parsers->n_parsers; i++) { |
| r = reglib_rule_parsers->rule_parsers[i](line, reg_rule); |
| if (r == 0) |
| break; |
| } |
| |
| return r; |
| } |
| |
| static uint32_t |
| reglib_get_n_rules(FILE *fp, struct ieee80211_reg_rule *reg_rule) |
| { |
| uint32_t n_rules = 0; |
| int r; |
| bool save_debug = false; |
| |
| save_debug = debug; |
| debug = false; |
| |
| while (1) { |
| r = reglib_parse_rule(fp, reg_rule); |
| if (r != 0) |
| break; |
| n_rules++; |
| } |
| |
| debug = save_debug; |
| |
| return n_rules; |
| } |
| |
| static int reglib_parse_reg_rule(FILE *fp, struct ieee80211_reg_rule *reg_rule) |
| { |
| int r; |
| |
| while (1) { |
| r = reglib_parse_rule(fp, reg_rule); |
| if (r != 0) |
| continue; |
| return 0; |
| } |
| } |
| |
| static struct ieee80211_regdomain * |
| reglib_parse_rules(FILE *fp, struct ieee80211_regdomain *trd) |
| { |
| struct ieee80211_regdomain *rd; |
| struct ieee80211_reg_rule rule; |
| struct ieee80211_reg_rule *reg_rule; |
| fpos_t pos; |
| unsigned int i; |
| uint32_t size_of_regd = 0, num_rules = 0; |
| int r; |
| |
| memset(&rule, 0, sizeof(rule)); |
| reg_rule = &rule; |
| |
| r = fgetpos(fp, &pos); |
| if (r != 0) { |
| fprintf(stderr, "fgetpos() failed: %s\n", |
| strerror(errno)); |
| return NULL; |
| } |
| |
| num_rules = reglib_get_n_rules(fp, reg_rule); |
| if (!num_rules) |
| return NULL; |
| |
| size_of_regd = reglib_array_len(sizeof(struct ieee80211_regdomain), |
| num_rules + 1, |
| sizeof(struct ieee80211_reg_rule)); |
| rd = malloc(size_of_regd); |
| if (!rd) |
| return NULL; |
| |
| memset(rd, 0, size_of_regd); |
| memcpy(rd, trd, sizeof(*trd)); |
| |
| rd->n_reg_rules = num_rules; |
| |
| r = fsetpos(fp, &pos); |
| if (r != 0) { |
| fprintf(stderr, "fsetpos() failed: %s\n", |
| strerror(errno)); |
| free(rd); |
| return NULL; |
| } |
| for (i = 0; i < num_rules; i++) { |
| struct ieee80211_reg_rule *rrule = &rd->reg_rules[i]; |
| |
| if (reglib_parse_reg_rule(fp, rrule) != 0) { |
| fprintf(stderr, "rule parse failed\n"); |
| free(rd); |
| return NULL; |
| } |
| } |
| return rd; |
| } |
| |
| static int |
| reglib_parse_country_simple(char *line, struct ieee80211_regdomain *rd) |
| { |
| char dfs_region_alpha[9]; |
| char alpha2[2]; |
| int hits; |
| |
| memset(rd, 0, sizeof(rd)); |
| memset(alpha2, 0, sizeof(alpha2)); |
| memset(dfs_region_alpha, 0, sizeof(dfs_region_alpha)); |
| |
| hits = sscanf(line, "country %2[a-zA-Z0-9]:", |
| alpha2); |
| |
| if (hits != 1) |
| return -EINVAL; |
| |
| rd->alpha2[0] = alpha2[0]; |
| rd->alpha2[1] = alpha2[1]; |
| |
| return 0; |
| } |
| |
| static int reglib_parse_country_dfs(char *line, struct ieee80211_regdomain *rd) |
| { |
| char dfs_region_alpha[9]; |
| char alpha2[2]; |
| int hits; |
| |
| memset(rd, 0, sizeof(rd)); |
| memset(alpha2, 0, sizeof(alpha2)); |
| memset(dfs_region_alpha, 0, sizeof(dfs_region_alpha)); |
| |
| hits = sscanf(line, "country %2[a-zA-Z0-9]:%*[ ]%s\n", |
| alpha2, |
| dfs_region_alpha); |
| if (hits <= 0) |
| return -EINVAL; |
| |
| if (hits != 2) |
| return -EINVAL; |
| |
| |
| rd->alpha2[0] = alpha2[0]; |
| rd->alpha2[1] = alpha2[1]; |
| rd->dfs_region = reglib_parse_dfs_region(dfs_region_alpha); |
| |
| return 0; |
| } |
| |
| struct ieee80211_regdomain *__reglib_parse_country(FILE *fp) |
| { |
| struct ieee80211_regdomain *rd; |
| struct ieee80211_regdomain tmp_rd; |
| char line[1024]; |
| char *line_p; |
| unsigned int i; |
| int r = 0; |
| struct reglib_country_parse_list *reglib_country_parsers; |
| size_t size_of_parsers = sizeof(struct reglib_country_parse_list) + |
| 2 * sizeof(int (*)(char *, struct ieee80211_regdomain *)); |
| |
| reglib_country_parsers = malloc(size_of_parsers); |
| if (!reglib_country_parsers) |
| return NULL; |
| memset(reglib_country_parsers, 0, size_of_parsers); |
| |
| reglib_country_parsers->n_parsers = 2; |
| reglib_country_parsers->country_parsers[0] = |
| reglib_parse_country_dfs; |
| reglib_country_parsers->country_parsers[1] = |
| reglib_parse_country_simple; |
| |
| memset(&tmp_rd, 0, sizeof(tmp_rd)); |
| memset(line, 0, sizeof(line)); |
| |
| line_p = fgets(line, sizeof(line), fp); |
| |
| if (line_p != line) |
| return NULL; |
| |
| for (i = 0; i < reglib_country_parsers->n_parsers; i++) { |
| r = reglib_country_parsers->country_parsers[i](line, &tmp_rd); |
| if (r == 0) |
| break; |
| } |
| |
| if (r != 0) { |
| fprintf(stderr, "Invalid country line: %s", line); |
| return NULL; |
| } |
| |
| rd = reglib_parse_rules(fp, &tmp_rd); |
| |
| return rd; |
| } |
| |
| static int reglib_find_next_country_stream(FILE *fp) |
| { |
| fpos_t prev_pos; |
| int r; |
| unsigned int i = 0; |
| |
| while(1) { |
| char line[1024]; |
| char *line_p; |
| |
| r = fgetpos(fp, &prev_pos); |
| if (r != 0) { |
| fprintf(stderr, "fgetpos() failed: %s\n", |
| strerror(errno)); |
| return r; |
| } |
| |
| memset(line, 0, sizeof(line)); |
| |
| line_p = fgets(line, sizeof(line), fp); |
| if (line_p == line) { |
| if (strspn(line, "\n") == strlen(line)) { |
| i++; |
| continue; |
| } |
| if (strncmp(line, "country", 7) != 0) |
| continue; |
| r = fsetpos(fp, &prev_pos); |
| if (r != 0) { |
| fprintf(stderr, "fsetpos() failed: %s\n", |
| strerror(errno)); |
| return r; |
| } |
| return 0; |
| } else |
| return EOF; |
| } |
| } |
| |
| struct ieee80211_regdomain *reglib_parse_country(FILE *fp) |
| { |
| int r; |
| |
| r = reglib_find_next_country_stream(fp); |
| if (r != 0) |
| return NULL; |
| return __reglib_parse_country(fp); |
| } |
| |
| FILE *reglib_create_parse_stream(FILE *f) |
| { |
| unsigned int lines = 0; |
| FILE *fp; |
| |
| fp = tmpfile(); |
| if (errno) { |
| fprintf(stderr, "%s\n", strerror(errno)); |
| return NULL; |
| } |
| |
| while(1) { |
| char line[1024]; |
| char *line_p; |
| |
| line_p = fgets(line, sizeof(line), f); |
| if (line_p == line) { |
| if (strchr(line, '#') == NULL) { |
| fputs(line, fp); |
| lines++; |
| } |
| continue; |
| } else |
| break; |
| } |
| |
| rewind(fp); |
| fflush(fp); |
| |
| return fp; |
| } |