| /** @file |
| |
| A brief file description |
| |
| @section license License |
| |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| /***************************************************************************** |
| * |
| * MatcherUtils.cc - Various helper routines used in ControlMatcher |
| * and ReverseProxy |
| * |
| * |
| ****************************************************************************/ |
| |
| #include "tscore/ink_platform.h" |
| #include "tscore/Diags.h" |
| #include "tscore/ink_memory.h" |
| #include "tscore/ink_inet.h" |
| #include "tscore/ink_assert.h" |
| #include "tscore/MatcherUtils.h" |
| #include "tscore/Tokenizer.h" |
| |
| // char* readIntoBuffer(const char* file_path, const char* module_name, |
| // int* read_size_ptr) |
| // |
| // Attempts to open and read arg file_path into a buffer allocated |
| // off the heap (via ats_malloc() ) Returns a pointer to the buffer |
| // is successful and nullptr otherwise. |
| // |
| // CALLEE is responsible for deallocating the buffer via ats_free() |
| // |
| char * |
| readIntoBuffer(const char *file_path, const char *module_name, int *read_size_ptr) |
| { |
| int fd; |
| struct stat file_info; |
| char *file_buf, *buf; |
| int read_size = 0; |
| int file_size; |
| |
| if (read_size_ptr != nullptr) { |
| *read_size_ptr = 0; |
| } |
| // Open the file for Blocking IO. We will be reading this |
| // at start up and infrequently afterward |
| if ((fd = open(file_path, O_RDONLY)) < 0) { |
| Error("%s Can not open %s file : %s", module_name, file_path, strerror(errno)); |
| return nullptr; |
| } |
| |
| if (fstat(fd, &file_info) < 0) { |
| Error("%s Can not stat %s file : %s", module_name, file_path, strerror(errno)); |
| close(fd); |
| return nullptr; |
| } |
| |
| file_size = file_info.st_size; // number of bytes in file |
| |
| if (file_size < 0) { |
| Error("%s Can not get correct file size for %s file : %" PRId64 "", module_name, file_path, (int64_t)file_info.st_size); |
| close(fd); |
| return nullptr; |
| } |
| |
| ink_assert(file_size >= 0); |
| |
| // Allocate a buffer large enough to hold the entire file |
| // File size should be small and this makes it easy to |
| // do two passes on the file |
| file_buf = static_cast<char *>(ats_malloc(file_size + 1)); |
| // Null terminate the buffer so that string operations will work |
| file_buf[file_size] = '\0'; |
| |
| int ret = 0; |
| buf = file_buf; // working pointer |
| |
| // loop over read, trying to read in as much as we can each time. |
| while (file_size > read_size) { |
| ret = read(fd, buf, file_size - read_size); |
| if (ret <= 0) { |
| break; |
| } |
| |
| buf += ret; |
| read_size += ret; |
| } |
| |
| buf = nullptr; // done with. don't want to accidentally use this instead of file_buf. |
| |
| // Check to make sure that we got the whole file |
| if (ret < 0) { |
| Error("%s Read of %s file failed : %s", module_name, file_path, strerror(errno)); |
| ats_free(file_buf); |
| file_buf = nullptr; |
| } else if (read_size < file_size) { |
| // Didn't get the whole file, drop everything. We don't want to return |
| // something partially read because, ie. with configs, the behaviour |
| // is undefined. |
| Error("%s Only able to read %d bytes out %d for %s file", module_name, read_size, file_size, file_path); |
| ats_free(file_buf); |
| file_buf = nullptr; |
| } |
| |
| if (file_buf && read_size_ptr) { |
| *read_size_ptr = read_size; |
| } |
| |
| close(fd); |
| |
| return file_buf; |
| } |
| |
| // int unescapifyStr(char* buffer) |
| // |
| // Unescapifies a URL without a making a copy. |
| // The passed in string is modified |
| // |
| int |
| unescapifyStr(char *buffer) |
| { |
| char *read = buffer; |
| char *write = buffer; |
| char subStr[3]; |
| |
| subStr[2] = '\0'; |
| while (*read != '\0') { |
| if (*read == '%' && *(read + 1) != '\0' && *(read + 2) != '\0') { |
| subStr[0] = *(++read); |
| subStr[1] = *(++read); |
| *write = static_cast<char>(strtol(subStr, (char **)nullptr, 16)); |
| read++; |
| write++; |
| } else if (*read == '+') { |
| *write = ' '; |
| write++; |
| read++; |
| } else { |
| *write = *read; |
| write++; |
| read++; |
| } |
| } |
| *write = '\0'; |
| |
| return (write - buffer); |
| } |
| |
| const char * |
| ExtractIpRange(char *match_str, in_addr_t *min, in_addr_t *max) |
| { |
| IpEndpoint ip_min, ip_max; |
| const char *zret = ExtractIpRange(match_str, &ip_min.sa, &ip_max.sa); |
| if (nullptr == zret) { // success |
| if (ats_is_ip4(&ip_min) && ats_is_ip4(&ip_max)) { |
| if (min) { |
| *min = ntohl(ats_ip4_addr_cast(&ip_min)); |
| } |
| if (max) { |
| *max = ntohl(ats_ip4_addr_cast(&ip_max)); |
| } |
| } else { |
| zret = "The addresses were not IPv4 addresses."; |
| } |
| } |
| return zret; |
| } |
| |
| // char* ExtractIpRange(char* match_str, sockaddr* addr1, |
| // sockaddr* addr2) |
| // |
| // Attempts to extract either an Ip Address or an IP Range |
| // from match_str. The range should be two addresses |
| // separated by a hyphen and no spaces |
| // |
| // If the extraction is successful, sets addr1 and addr2 |
| // to the extracted values (in the case of a single |
| // address addr2 = addr1) and returns nullptr |
| // |
| // If the extraction fails, returns a static string |
| // that describes the reason for the error. |
| // |
| const char * |
| ExtractIpRange(char *match_str, sockaddr *addr1, sockaddr *addr2) |
| { |
| Tokenizer rangeTok("-/"); |
| bool mask = strchr(match_str, '/') != nullptr; |
| int mask_bits; |
| int mask_val; |
| int numToks; |
| IpEndpoint la1, la2; |
| |
| // Extract the IP addresses from match data |
| numToks = rangeTok.Initialize(match_str, SHARE_TOKS); |
| |
| if (numToks < 0) { |
| return "no IP address given"; |
| } else if (numToks > 2) { |
| return "malformed IP range"; |
| } |
| |
| if (0 != ats_ip_pton(rangeTok[0], &la1.sa)) { |
| return "malformed IP address"; |
| } |
| |
| // Handle a IP range |
| if (numToks == 2) { |
| if (mask) { |
| if (!ats_is_ip4(&la1)) { |
| return "Masks supported only for IPv4"; |
| } |
| // coverity[secure_coding] |
| if (sscanf(rangeTok[1], "%d", &mask_bits) != 1) { |
| return "bad mask specification"; |
| } |
| |
| if (!(mask_bits >= 0 && mask_bits <= 32)) { |
| return "invalid mask specification"; |
| } |
| |
| if (mask_bits == 32) { |
| mask_val = 0; |
| } else { |
| mask_val = htonl(0xffffffff >> mask_bits); |
| } |
| in_addr_t a = ats_ip4_addr_cast(&la1); |
| ats_ip4_set(&la2, a | mask_val); |
| ats_ip4_set(&la1, a & (mask_val ^ 0xffffffff)); |
| |
| } else { |
| if (0 != ats_ip_pton(rangeTok[1], &la2)) { |
| return "malformed ip address at range end"; |
| } |
| } |
| |
| if (1 == ats_ip_addr_cmp(&la1.sa, &la2.sa)) { |
| return "range start greater than range end"; |
| } |
| |
| ats_ip_copy(addr2, &la2); |
| } else { |
| ats_ip_copy(addr2, &la1); |
| } |
| |
| ats_ip_copy(addr1, &la1); |
| return nullptr; |
| } |
| |
| // char* tokLine(char* buf, char** last, char cont) |
| // |
| // Similar to strtok_r but only tokenizes on '\n' |
| // and will return tokens that are empty strings |
| // |
| char * |
| tokLine(char *buf, char **last, char cont) |
| { |
| char *start; |
| char *cur; |
| char *prev = nullptr; |
| |
| if (buf != nullptr) { |
| start = cur = buf; |
| *last = buf; |
| } else { |
| start = cur = (*last) + 1; |
| } |
| |
| while (*cur != '\0') { |
| if (*cur == '\n') { |
| if (cont != '\0' && prev != nullptr && *prev == cont) { |
| *prev = ' '; |
| *cur = ' '; |
| } else { |
| *cur = '\0'; |
| *last = cur; |
| return start; |
| } |
| } |
| prev = cur++; |
| } |
| |
| // Return the last line even if it does |
| // not end in a newline |
| if (cur > (*last + 1)) { |
| *last = cur - 1; |
| return start; |
| } |
| |
| return nullptr; |
| } |
| |
| const char *matcher_type_str[] = {"invalid", "host", "domain", "ip", "url_regex", "url", "host_regex"}; |
| |
| // char* processDurationString(char* str, int* seconds) |
| // |
| // Take a duration sting which is composed of |
| // digits followed by a unit specifier |
| // w - week |
| // d - day |
| // h - hour |
| // m - min |
| // s - sec |
| // |
| // Trailing digits without a specifier are |
| // assumed to be seconds |
| // |
| // Returns nullptr on success and a static |
| // error string on failure |
| // |
| const char * |
| processDurationString(char *str, int *seconds) |
| { |
| char *s = str; |
| char *current = str; |
| char unit; |
| int tmp; |
| int multiplier; |
| int result = 0; |
| int len; |
| |
| if (str == nullptr) { |
| return "Missing time"; |
| } |
| |
| len = strlen(str); |
| for (int i = 0; i < len; i++) { |
| if (!ParseRules::is_digit(*current)) { |
| // Make sure there is a time to proces |
| if (current == s) { |
| return "Malformed time"; |
| } |
| |
| unit = *current; |
| |
| switch (unit) { |
| case 'w': |
| multiplier = 7 * 24 * 60 * 60; |
| break; |
| case 'd': |
| multiplier = 24 * 60 * 60; |
| break; |
| case 'h': |
| multiplier = 60 * 60; |
| break; |
| case 'm': |
| multiplier = 60; |
| break; |
| case 's': |
| multiplier = 1; |
| break; |
| case '-': |
| return "Negative time not permitted"; |
| default: |
| return "Invalid time unit specified"; |
| } |
| |
| *current = '\0'; |
| |
| // coverity[secure_coding] |
| if (sscanf(s, "%d", &tmp) != 1) { |
| // Really should not happen since everything |
| // in the string is digit |
| ink_assert(0); |
| return "Malformed time"; |
| } |
| |
| result += (multiplier * tmp); |
| s = current + 1; |
| } |
| current++; |
| } |
| |
| // Read any trailing seconds |
| if (current != s) { |
| // coverity[secure_coding] |
| if (sscanf(s, "%d", &tmp) != 1) { |
| // Really should not happen since everything |
| // in the string is digit |
| ink_assert(0); |
| return "Malformed time"; |
| } else { |
| result += tmp; |
| } |
| } |
| // We rolled over the int |
| if (result < 0) { |
| return "Time too big"; |
| } |
| |
| *seconds = result; |
| return nullptr; |
| } |
| |
| const matcher_tags http_dest_tags = {"dest_host", "dest_domain", "dest_ip", "url_regex", "url", "host_regex", true}; |
| |
| const matcher_tags ip_allow_src_tags = {nullptr, nullptr, "src_ip", nullptr, nullptr, nullptr, false}; |
| |
| const matcher_tags ip_allow_dest_tags = {nullptr, nullptr, "dest_ip", nullptr, nullptr, nullptr, true}; |
| |
| const matcher_tags socks_server_tags = {nullptr, nullptr, "dest_ip", nullptr, nullptr, nullptr, false}; |
| |
| // char* parseConfigLine(char* line, matcher_line* p_line, |
| // const matcher_tags* tags) |
| // |
| // Parse out a config file line suitable for passing to |
| // a ControlMatcher object |
| // |
| // If successful, nullptr is returned. If unsuccessful, |
| // a static error string is returned |
| // |
| const char * |
| parseConfigLine(char *line, matcher_line *p_line, const matcher_tags *tags) |
| { |
| enum pState { |
| FIND_LABEL, |
| PARSE_LABEL, |
| PARSE_VAL, |
| START_PARSE_VAL, |
| CONSUME, |
| }; |
| |
| pState state = FIND_LABEL; |
| bool inQuote = false; |
| char *copyForward = nullptr; |
| char *copyFrom = nullptr; |
| char *s = line; |
| char *label = nullptr; |
| char *val = nullptr; |
| int num_el = 0; |
| matcher_type type = MATCH_NONE; |
| |
| // Zero out the parsed line structure |
| memset(p_line, 0, sizeof(matcher_line)); |
| |
| if (*s == '\0') { |
| return nullptr; |
| } |
| |
| do { |
| switch (state) { |
| case FIND_LABEL: |
| if (!isspace(*s)) { |
| state = PARSE_LABEL; |
| label = s; |
| } |
| s++; |
| break; |
| case PARSE_LABEL: |
| if (*s == '=') { |
| *s = '\0'; |
| state = START_PARSE_VAL; |
| } |
| s++; |
| break; |
| case START_PARSE_VAL: |
| // Init state needed for parsing values |
| copyForward = nullptr; |
| copyFrom = nullptr; |
| |
| if (*s == '"') { |
| inQuote = true; |
| val = s + 1; |
| } else if (*s == '\\') { |
| inQuote = false; |
| val = s + 1; |
| } else { |
| inQuote = false; |
| val = s; |
| } |
| |
| if (inQuote == false && (isspace(*s) || *(s + 1) == '\0')) { |
| state = CONSUME; |
| } else { |
| state = PARSE_VAL; |
| } |
| |
| s++; |
| break; |
| case PARSE_VAL: |
| if (inQuote == true) { |
| if (*s == '\\') { |
| // The next character is escaped |
| // |
| // To remove the escaped character |
| // we need to copy |
| // the rest of the entry over it |
| // but since we do not know where the |
| // end is right now, defer the work |
| // into the future |
| |
| if (copyForward != nullptr) { |
| // Perform the prior copy forward |
| int bytesCopy = s - copyFrom; |
| memcpy(copyForward, copyFrom, s - copyFrom); |
| ink_assert(bytesCopy > 0); |
| |
| copyForward += bytesCopy; |
| copyFrom = s + 1; |
| } else { |
| copyForward = s; |
| copyFrom = s + 1; |
| } |
| |
| // Scroll past the escape character |
| s++; |
| |
| // Handle the case that places us |
| // at the end of the file |
| if (*s == '\0') { |
| break; |
| } |
| } else if (*s == '"') { |
| state = CONSUME; |
| *s = '\0'; |
| } |
| } else if ((*s == '\\' && ParseRules::is_digit(*(s + 1))) || !ParseRules::is_char(*s)) { |
| // INKqa10511 |
| // traffic server need to handle unicode characters |
| // right now ignore the entry |
| return "Unrecognized encoding scheme"; |
| } else if (isspace(*s)) { |
| state = CONSUME; |
| *s = '\0'; |
| } |
| |
| s++; |
| |
| // If we are now at the end of the line, |
| // we need to consume final data |
| if (*s == '\0') { |
| state = CONSUME; |
| } |
| break; |
| case CONSUME: |
| break; |
| } |
| |
| if (state == CONSUME) { |
| // See if there are any quote copy overs |
| // we've pushed into the future |
| if (copyForward != nullptr) { |
| int toCopy = (s - 1) - copyFrom; |
| memcpy(copyForward, copyFrom, toCopy); |
| *(copyForward + toCopy) = '\0'; |
| } |
| |
| p_line->line[0][num_el] = label; |
| p_line->line[1][num_el] = val; |
| type = MATCH_NONE; |
| |
| // Check to see if this the primary specifier we are looking for |
| if (tags->match_ip && strcasecmp(tags->match_ip, label) == 0) { |
| type = MATCH_IP; |
| } else if (tags->match_host && strcasecmp(tags->match_host, label) == 0) { |
| type = MATCH_HOST; |
| } else if (tags->match_domain && strcasecmp(tags->match_domain, label) == 0) { |
| type = MATCH_DOMAIN; |
| } else if (tags->match_regex && strcasecmp(tags->match_regex, label) == 0) { |
| type = MATCH_REGEX; |
| } else if (tags->match_url && strcasecmp(tags->match_url, label) == 0) { |
| type = MATCH_URL; |
| } else if (tags->match_host_regex && strcasecmp(tags->match_host_regex, label) == 0) { |
| type = MATCH_HOST_REGEX; |
| } |
| // If this a destination tag, use it |
| if (type != MATCH_NONE) { |
| // Check to see if this second destination specifier |
| if (p_line->type != MATCH_NONE) { |
| if (tags->dest_error_msg == false) { |
| return "Multiple Sources Specified"; |
| } else { |
| return "Multiple Destinations Specified"; |
| } |
| } else { |
| p_line->dest_entry = num_el; |
| p_line->type = type; |
| } |
| } |
| num_el++; |
| |
| if (num_el > MATCHER_MAX_TOKENS) { |
| return "Malformed line: Too many tokens"; |
| } |
| |
| state = FIND_LABEL; |
| } |
| } while (*s != '\0'); |
| |
| p_line->num_el = num_el; |
| |
| if (state != CONSUME && state != FIND_LABEL) { |
| return "Malformed entry"; |
| } |
| |
| if (!tags->empty() && p_line->type == MATCH_NONE) { |
| if (tags->dest_error_msg == false) { |
| return "No source specifier"; |
| } else { |
| return "No destination specifier"; |
| } |
| } |
| |
| return nullptr; |
| } |