diff --git a/README.md b/README.md index 2cf1b124..9b4741f4 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,8 @@ Configuration Syntax # Combining regular expression and wildcard will resolve the hostname # client requested and proxy to it .*\\.edu *:443 + # Also possible to rewrite target from source using capture groups + ([a-z]+)\.public\.com $1.internal.local } DNS Resolution diff --git a/src/address.c b/src/address.c index 9a544a5b..4f833761 100644 --- a/src/address.c +++ b/src/address.c @@ -42,6 +42,7 @@ struct Address { HOSTNAME, SOCKADDR, WILDCARD, + PATTERN, } type; size_t len; /* length of data */ @@ -123,6 +124,20 @@ new_address(const char *hostname_or_ip) { } } + if (strchr(hostname_or_ip, '$') != NULL) { + len = strlen(hostname_or_ip); + struct Address *addr = malloc( + offsetof(struct Address, data) + len + 1); + if (addr != NULL) { + addr->type = PATTERN; + addr->port = 0; + addr->len = len; + memcpy(addr->data, hostname_or_ip, len); + addr->data[addr->len] = '\0'; + } + return addr; + } + /* Wildcard */ if (strcmp("*", hostname_or_ip) == 0) { struct Address *addr = malloc(sizeof(struct Address)); @@ -214,6 +229,7 @@ size_t address_len(const struct Address *addr) { switch (addr->type) { case HOSTNAME: + case PATTERN: /* include trailing null byte */ return offsetof(struct Address, data) + addr->len + 1; case SOCKADDR: @@ -274,6 +290,11 @@ address_is_wildcard(const struct Address *addr) { return addr != NULL && addr->type == WILDCARD; } +int +address_is_pattern(const struct Address * addr) { + return addr != NULL && addr->type == PATTERN; +} + const char * address_hostname(const struct Address *addr) { if (addr->type != HOSTNAME) @@ -282,6 +303,14 @@ address_hostname(const struct Address *addr) { return addr->data; } +const char * +address_pattern(const struct Address *addr) { + if (addr->type != PATTERN) + return NULL; + + return addr->data; +} + const struct sockaddr * address_sa(const struct Address *addr) { if (addr->type != SOCKADDR) @@ -319,6 +348,7 @@ address_port(const struct Address *addr) { return 0; } case WILDCARD: + case PATTERN: return addr->port; default: /* invalid Address type */ @@ -350,6 +380,7 @@ address_set_port(struct Address *addr, uint16_t port) { /* fall through */ case HOSTNAME: case WILDCARD: + case PATTERN: addr->port = port; break; default: @@ -375,6 +406,7 @@ display_address(const struct Address *addr, char *buffer, size_t buffer_len) { switch (addr->type) { case HOSTNAME: + case PATTERN: if (addr->port != 0) snprintf(buffer, buffer_len, "%s:%" PRIu16, addr->data, diff --git a/src/address.h b/src/address.h index 66125eb6..c687b75d 100644 --- a/src/address.h +++ b/src/address.h @@ -45,9 +45,11 @@ struct Address *copy_address(const struct Address *); size_t address_len(const struct Address *); int address_compare(const struct Address *, const struct Address *); int address_is_hostname(const struct Address *); +int address_is_pattern(const struct Address *); int address_is_sockaddr(const struct Address *); int address_is_wildcard(const struct Address *); const char *address_hostname(const struct Address *); +const char *address_pattern(const struct Address *); const struct sockaddr *address_sa(const struct Address *); socklen_t address_sa_len(const struct Address *); uint16_t address_port(const struct Address *); diff --git a/src/backend.c b/src/backend.c index a6320e95..c432a5ff 100644 --- a/src/backend.c +++ b/src/backend.c @@ -98,7 +98,6 @@ init_backend(struct Backend *backend) { if (backend->pattern_re == NULL) { const char *reerr; int reerroffset; - backend->pattern_re = pcre_compile(backend->pattern, 0, &reerr, &reerroffset, NULL); if (backend->pattern_re == NULL) { @@ -117,8 +116,9 @@ init_backend(struct Backend *backend) { return 1; } -struct Backend * +struct BackendLookupResult lookup_backend(const struct Backend_head *head, const char *name, size_t name_len) { + struct BackendLookupResult result; struct Backend *iter; if (name == NULL) { @@ -128,12 +128,57 @@ lookup_backend(const struct Backend_head *head, const char *name, size_t name_le STAILQ_FOREACH(iter, head, entries) { assert(iter->pattern_re != NULL); - if (pcre_exec(iter->pattern_re, NULL, - name, name_len, 0, 0, NULL, 0) >= 0) - return iter; + + if ((result.matches[0] = pcre_exec(iter->pattern_re, NULL, + name, name_len, 0, 0, result.matches + 1, 31)) >= 0) { + + result.backend = iter; + return result; + } } - return NULL; + result.backend = NULL; + + return result; +} + +int apply_pattern(const char * name, const char * pattern, int * matches, char * dst, size_t result_len) +{ + int length = result_len > 1024 ? 1024 : result_len; + const char * src = pattern; + while(length > 0 && (*src)) { + if (src[0] == '$' && src[1] != 0) { + if (src[1] >= '0' && src[1] <= '9') { + int stringnumber = src[1] - '0'; + int ret; + src += 2; + + *dst=0; + ret = pcre_copy_substring(name, matches + 1, matches[0], stringnumber, dst, length); + if (ret < 0) { + return 0; + } + dst += ret; + length -= ret; + } else { + *dst = src[1]; + dst++; + length--; + src += 2; + } + + } else { + *dst = *src; + dst++; + src++; + length--; + } + } + if (length) { + *dst = 0; + return 1; + } + return 0; } void diff --git a/src/backend.h b/src/backend.h index 9fdffb07..e25404f6 100644 --- a/src/backend.h +++ b/src/backend.h @@ -43,13 +43,19 @@ struct Backend { STAILQ_ENTRY(Backend) entries; }; +struct BackendLookupResult { + struct Backend * backend; + int matches[32]; +}; + void add_backend(struct Backend_head *, struct Backend *); int init_backend(struct Backend *); -struct Backend *lookup_backend(const struct Backend_head *, const char *, size_t); +struct BackendLookupResult lookup_backend(const struct Backend_head *, const char *, size_t); void print_backend_config(FILE *, const struct Backend *); void remove_backend(struct Backend_head *, struct Backend *); struct Backend *new_backend(); int accept_backend_arg(struct Backend *, const char *); +int apply_pattern(const char * name, const char * pattern, int * matches, char *, size_t); #endif diff --git a/src/listener.c b/src/listener.c index ce5956da..040f0829 100644 --- a/src/listener.c +++ b/src/listener.c @@ -627,6 +627,51 @@ listener_lookup_server_address(const struct Listener *listener, .address = listener->fallback_address, .use_proxy_header = listener->fallback_use_proxy_header }; + } else if (address_is_pattern(table_result.address)) { + /* Pattern table entry, create a new address from hostname */ + char replacement[1024]; + if (apply_pattern(name, address_pattern(table_result.address) ,table_result.matches, replacement, 1024) == 0) { + warn("Failed pattern %.*s in client request", + (int)name_len, name); + + return (struct LookupResult){ + .address = listener->fallback_address, + .use_proxy_header = listener->fallback_use_proxy_header + }; + } + struct Address *new_addr = new_address(replacement); + if (new_addr == NULL) { + warn("Failed pattern%.*s in client request", + (int)name_len, name); + + return (struct LookupResult){ + .address = listener->fallback_address, + .use_proxy_header = listener->fallback_use_proxy_header + }; + } else if (address_is_sockaddr(new_addr)) { + warn("Refusing to proxy to socket address literal %.*s in request", + (int)name_len, name); + free(new_addr); + + return (struct LookupResult){ + .address = listener->fallback_address, + .use_proxy_header = listener->fallback_use_proxy_header + }; + } + + /* We created a valid new_addr, use the port from wildcard address if + * present otherwise the listener */ + address_set_port(new_addr, address_port(table_result.address) != 0 ? + address_port(table_result.address) : + address_port(listener->address)); + + + return (struct LookupResult){ + .address = new_addr, + .caller_free_address = 1, + .use_proxy_header = table_result.use_proxy_header + }; + } else if (address_is_wildcard(table_result.address)) { /* Wildcard table entry, create a new address from hostname */ struct Address *new_addr = new_address(name); diff --git a/src/table.c b/src/table.c index 9eb68ec9..7730d2b3 100644 --- a/src/table.c +++ b/src/table.c @@ -37,7 +37,7 @@ static void free_table(struct Table *); -static inline struct Backend * +static inline struct BackendLookupResult table_lookup_backend(const struct Table *table, const char *name, size_t name_len) { return lookup_backend(&table->backends, name, name_len); } @@ -130,14 +130,20 @@ remove_table(struct Table_head *tables, struct Table *table) { struct LookupResult table_lookup_server_address(const struct Table *table, const char *name, size_t name_len) { - struct Backend *b = table_lookup_backend(table, name, name_len); - if (b == NULL) { + struct BackendLookupResult b = table_lookup_backend(table, name, name_len); + if (b.backend == NULL) { info("No match found for %.*s", (int)name_len, name); return (struct LookupResult){.address = NULL}; } - return (struct LookupResult){.address = b->address, - .use_proxy_header = b->use_proxy_header}; + struct LookupResult result; + result.address = b.backend->address; + result.caller_free_address = 0; + result.use_proxy_header = b.backend->use_proxy_header; + for(int i = 0; i < 32; ++i) { + result.matches[i] = b.matches[i]; + } + return result; } void diff --git a/src/table.h b/src/table.h index 1ad9758f..9f08ef4b 100644 --- a/src/table.h +++ b/src/table.h @@ -49,6 +49,7 @@ struct LookupResult { const struct Address *address; int caller_free_address; int use_proxy_header; + int matches[32]; }; struct Table *new_table();