/* usbredirfilter.h usb redirection filter header Copyright 2012 Red Hat, Inc. Red Hat Authors: Hans de Goede This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, see . */ #include #include #include #ifdef WIN32 #include "strtok_r.h" #define strtok_r glibc_strtok_r #endif #include "usbredirfilter.h" int usbredirfilter_string_to_rules( const char *filter_str, const char *token_sep, const char *rule_sep, struct usbredirfilter_rule **rules_ret, int *rules_count_ret) { char *rule, *rule_saveptr, *token, *token_saveptr, *ep; struct usbredirfilter_rule *rules = NULL; int i, rules_count, *values, ret = 0; char *buf = NULL; const char *r; *rules_ret = NULL; *rules_count_ret = 0; /* Figure out how much rules there are in the file, so we know how much memory we must allocate for the rules array. Note this will come up with a slightly too large number if there are empty rule strings in the set. */ r = filter_str; rules_count = 0; while (r) { r = strchr(r, rule_sep[0]); if (r) r++; rules_count++; } rules = calloc(rules_count, sizeof(struct usbredirfilter_rule)); if (!rules) return -ENOMEM; /* Make a copy since strtok mangles the string */ buf = strdup(filter_str); if (!buf) { ret = -ENOMEM; goto leave; } /* And actually parse the string */ rules_count = 0; rule = strtok_r(buf, rule_sep, &rule_saveptr); while (rule) { /* We treat the filter rule as an array of ints for easier parsing */ values = (int *)&rules[rules_count]; token = strtok_r(rule, token_sep, &token_saveptr); for (i = 0; i < 5 && token; i++) { values[i] = strtol(token, &ep, 0); if (*ep) break; token = strtok_r(NULL, token_sep, &token_saveptr); } if (i != 5 || token != NULL || usbredirfilter_verify(&rules[rules_count], 1)) { ret = -EINVAL; goto leave; } rules_count++; rule = strtok_r(NULL, rule_sep, &rule_saveptr); } *rules_ret = rules; *rules_count_ret = rules_count; leave: if (ret) free(rules); free(buf); return ret; } char *usbredirfilter_rules_to_string(const struct usbredirfilter_rule *rules, int rules_count, const char *token_sep, const char *rule_sep) { int i; char *str, *p; if (usbredirfilter_verify(rules, rules_count)) return NULL; /* We need 28 bytes per rule in the worst case */ str = malloc(28 * rules_count + 1); if (!str) return NULL; p = str; for (i = 0; i < rules_count; i++) { if (rules[i].device_class != -1) p += sprintf(p, "0x%02x%c", rules[i].device_class, *token_sep); else p += sprintf(p, "-1%c", *token_sep); if (rules[i].vendor_id != -1) p += sprintf(p, "0x%04x%c", rules[i].vendor_id, *token_sep); else p += sprintf(p, "-1%c", *token_sep); if (rules[i].product_id != -1) p += sprintf(p, "0x%04x%c", rules[i].product_id, *token_sep); else p += sprintf(p, "-1%c", *token_sep); if (rules[i].device_version_bcd != -1) p += sprintf(p, "0x%04x%c", rules[i].device_version_bcd, *token_sep); else p += sprintf(p, "-1%c", *token_sep); p += sprintf(p, "%d%c", rules[i].allow ? 1:0, *rule_sep); } return str; } static int usbredirfilter_check1(const struct usbredirfilter_rule *rules, int rules_count, uint8_t device_class, uint16_t vendor_id, uint16_t product_id, uint16_t device_version_bcd, int default_allow) { int i; for (i = 0; i < rules_count; i++) { if ((rules[i].device_class == -1 || rules[i].device_class == device_class) && (rules[i].vendor_id == -1 || rules[i].vendor_id == vendor_id) && (rules[i].product_id == -1 || rules[i].product_id == product_id) && (rules[i].device_version_bcd == -1 || rules[i].device_version_bcd == device_version_bcd)) { /* Found a match ! */ return rules[i].allow ? 0 : -EPERM; } } return default_allow ? 0 : -ENOENT; } int usbredirfilter_check( const struct usbredirfilter_rule *rules, int rules_count, uint8_t device_class, uint8_t device_subclass, uint8_t device_protocol, uint8_t *interface_class, uint8_t *interface_subclass, uint8_t *interface_protocol, int interface_count, uint16_t vendor_id, uint16_t product_id, uint16_t device_version_bcd, int flags) { int i, rc, num_skipped=0; if (usbredirfilter_verify(rules, rules_count)) return -EINVAL; /* Check the device_class */ if (device_class != 0x00 && device_class != 0xef) { rc = usbredirfilter_check1(rules, rules_count, device_class, vendor_id, product_id, device_version_bcd, flags & usbredirfilter_fl_default_allow); if (rc) return rc; } /* Check the interface classes */ for (i = 0; i < interface_count; i++) { if (!(flags & usbredirfilter_fl_dont_skip_non_boot_hid) && interface_count > 1 && interface_class[i] == 0x03 && interface_subclass[i] == 0x00 && interface_protocol[i] == 0x00) { num_skipped++; continue; } rc = usbredirfilter_check1(rules, rules_count, interface_class[i], vendor_id, product_id, device_version_bcd, flags & usbredirfilter_fl_default_allow); if (rc) return rc; } /* If all interfaces were skipped, then force check on that device, * by recursively calling this function with a flag that forbids * skipping (usbredirfilter_fl_dont_skip_non_boot_hid) */ if (interface_count > 0 && num_skipped == interface_count) { rc = usbredirfilter_check(rules, rules_count, device_class, device_subclass, device_protocol, interface_class, interface_subclass, interface_protocol, interface_count, vendor_id, product_id, device_version_bcd, flags | usbredirfilter_fl_dont_skip_non_boot_hid); return rc; } return 0; } int usbredirfilter_verify( const struct usbredirfilter_rule *rules, int rules_count) { int i; for (i = 0; i < rules_count; i++) { if (rules[i].device_class < -1 || rules[i].device_class > 255) return -EINVAL; if (rules[i].vendor_id < -1 || rules[i].vendor_id > 65535) return -EINVAL; if (rules[i].product_id < -1 || rules[i].product_id > 65535) return -EINVAL; if (rules[i].device_version_bcd < -1 || rules[i].device_version_bcd > 65535) return -EINVAL; } return 0; } void usbredirfilter_print( const struct usbredirfilter_rule *rules, int rules_count, FILE *out) { int i; char device_class[16], vendor[16], product[16], version[16]; for (i = 0; i < rules_count; i++) { if (rules[i].device_class != -1) sprintf(device_class, " %02x", rules[i].device_class); else strcpy(device_class, "ANY"); if (rules[i].vendor_id != -1) sprintf(vendor, "%04x", rules[i].vendor_id); else strcpy(vendor, " ANY"); if (rules[i].product_id != -1) sprintf(product, "%04x", rules[i].product_id); else strcpy(product, " ANY"); if (rules[i].device_version_bcd != -1) sprintf(version, "%2d.%02d", ((rules[i].device_version_bcd & 0xf000) >> 12) * 10 + ((rules[i].device_version_bcd & 0x0f00) >> 8), ((rules[i].device_version_bcd & 0x00f0) >> 4) * 10 + ((rules[i].device_version_bcd & 0x000f))); else strcpy(version, " ANY"); fprintf(out, "Class %s ID %s:%s Version %s %s\n", device_class, vendor, product, version, rules[i].allow ? "Allow":"Block"); } }