├── .gitignore ├── test ├── gdb-vscode-root ├── whitelist_test.conf ├── whitelist_test.sh └── nslookup_test.sh ├── update_submodule.sh ├── .gitmodules ├── .vscode ├── tasks.json └── launch.json ├── Makefile ├── dnsmasq_regex_example.conf ├── patches ├── 002-regex-ipset.patch └── 001-regex-server.patch └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /patches/*.patched 2 | -------------------------------------------------------------------------------- /test/gdb-vscode-root: -------------------------------------------------------------------------------- 1 | pkexec /usr/bin/gdb "$@" 2 | -------------------------------------------------------------------------------- /update_submodule.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | git submodule update --init --recursive 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "dnsmasq"] 2 | path = dnsmasq 3 | url = git://thekelleys.org.uk/dnsmasq.git 4 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Compile", 6 | "type": "shell", 7 | "command": "make submodule -j8 BUILD_DEBUG=1", 8 | "problemMatcher": [], 9 | "group": { 10 | "kind": "build", 11 | "isDefault": true 12 | } 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /test/whitelist_test.conf: -------------------------------------------------------------------------------- 1 | # Issue: missing not equal to [^...] support, default nameserver (#) is broken 2 | # https://github.com/lixingcong/dnsmasq-regex/issues/8 3 | 4 | port=30000 5 | 6 | no-poll 7 | no-resolv 8 | 9 | server=114.114.114.114 10 | 11 | # Blacklist ALL, return 0.0.0.0 if not whitelisted 12 | address=/*/# 13 | 14 | # Whitelist any domain that ends in .edu 15 | server=/*.edu/# 16 | 17 | # Whilelist regex domain 18 | server=/:\.google\.(com|cn):/# 19 | server=/:\.msn\.(com|cn):/1.1.1.1 20 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Run Dnsmasq", 6 | "type": "cppdbg", 7 | "request": "launch", 8 | "program": "${workspaceFolder}/dnsmasq/src/dnsmasq", 9 | "args": ["-d", "-C", "/tmp/dnsmasq_regex_example.conf", "-q"], 10 | "stopAtEntry": false, 11 | "cwd": "${workspaceFolder}/dnsmasq", 12 | "environment": [], 13 | "externalConsole": false, 14 | "MIMode": "gdb", 15 | "miDebuggerPath" : "${workspaceFolder}/test/gdb-vscode-root", 16 | "preLaunchTask":"Compile", 17 | "setupCommands": [ 18 | { 19 | "description": "Enable pretty-printing for gdb", 20 | "text": "-enable-pretty-printing", 21 | "ignoreFailures": true 22 | } 23 | ] 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /test/whitelist_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # for debug only 4 | # ./src/dnsmasq -d -C /tmp/whitelist_test.conf -q 5 | 6 | DIG="dig @localhost -p30000 +retry=0" 7 | REDIR=/dev/null 8 | 9 | trap "exit" INT 10 | 11 | function printExit() 12 | { 13 | echo "Failed, exited" 14 | exit 1 15 | } 16 | 17 | echo 'UDP, server' 18 | domains=("jd.com" "www.fb.me" "educause.edu" "www.msn.com" "msn.com" "www.google.com" "google.com") 19 | expect_dns=("0.0.0.0" "0.0.0.0" "114.114.114.114" "1.1.1.1" "0.0.0.0" "114.114.114.114" "0.0.0.0") 20 | arraylength=${#domains[@]} 21 | for (( i=0; i<${arraylength}; i++ ));do 22 | domain=${domains[$i]} 23 | echo " $domain is forwared to ${expect_dns[$i]}" 24 | $DIG $domain > $REDIR || printExit 25 | done 26 | 27 | echo 'TCP, server' 28 | domains=("www.jd.com" "api.github.com" "berkeley.edu" "msnbc.msn.com" "msn.cn" "play.google.com" "google.cn") 29 | expect_dns=("0.0.0.0" "0.0.0.0" "114.114.114.114" "1.1.1.1" "0.0.0.0" "114.114.114.114" "0.0.0.0") 30 | arraylength=${#domains[@]} 31 | for (( i=0; i<${arraylength}; i++ ));do 32 | domain=${domains[$i]} 33 | echo " $domain is forwared to ${expect_dns[$i]}" 34 | $DIG $domain +tcp > $REDIR || printExit 35 | done 36 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BIN := dnsmasq/src/dnsmasq 2 | 3 | PATCH_DIR := patches 4 | PATCHES := $(sort $(wildcard $(PATCH_DIR)/*.patch)) 5 | PATCHED := $(sort $(patsubst $(PATCH_DIR)/%.patch, $(PATCH_DIR)/%.patched, $(PATCHES))) 6 | 7 | # turn on/off for regex or regex_ipset 8 | DNSMASQ_COPTS="-DHAVE_REGEX -DHAVE_REGEX_IPSET -DHAVE_NFTSET" 9 | 10 | all:$(BIN) 11 | 12 | .PHONY: submodule 13 | submodule: 14 | cd dnsmasq && $(MAKE) COPTS=$(DNSMASQ_COPTS) 15 | 16 | $(BIN):$(PATCHED) 17 | cd dnsmasq && $(MAKE) COPTS=$(DNSMASQ_COPTS) 18 | $(MAKE) remove_patched 19 | $(MAKE) reset_submodule 20 | 21 | # disable parallel build for patching files 22 | # for preventing from producing out of order chunks 23 | .NOTPARALLEL: %.patched 24 | %.patched:%.patch 25 | @echo "Applying $^" 26 | @patch -p 1 -d dnsmasq < $^ && touch $@ 27 | @echo 28 | 29 | .PHONY: reset_submodule 30 | reset_submodule: 31 | git submodule foreach --recursive git reset --hard 32 | 33 | .PHONY: remove_patched 34 | remove_patched: 35 | find . \( -name \*.orig -o -name \*.rej \) -delete 36 | rm -rf $(PATCHED) 37 | 38 | .PHONY: clean 39 | clean: 40 | $(MAKE) -C dnsmasq clean 41 | $(MAKE) remove_patched 42 | $(MAKE) reset_submodule 43 | -------------------------------------------------------------------------------- /dnsmasq_regex_example.conf: -------------------------------------------------------------------------------- 1 | # listening DNS port 2 | port=30000 3 | 4 | # do not read /etc/resolv.conf at all 5 | no-resolv 6 | 7 | # do not re-read /etc/resolv.conf when file was changed by ISP 8 | no-poll 9 | 10 | # default upstream dns server 11 | server=114.114.114.114 12 | 13 | # regex server examples 14 | server=/:.*github.*:/8.8.8.8 15 | server=/:.*google.*:/8.8.8.8 16 | server=/:.*youtube.*:/8.8.8.8 17 | 18 | # normal server examples 19 | server=/fb.me/8.8.8.8 20 | server=/163.com/223.5.5.5 21 | 22 | # regex address examples, match 'xxx.qq.com' and 'xxx.q.com' 23 | address=/:\.q{1,2}\.com$:/127.0.0.1 24 | 25 | # normal address examples 26 | address=/localhost.com/127.0.0.1 27 | 28 | # ipset examples 29 | # 30 | # Tips: You need to manually create ipset 'test' first: 31 | # sudo ipset create test hash:ip 32 | # 33 | # and run dnsmasq with root(to modify this set), review the set: 34 | # sudo ipset list 35 | # 36 | # after test, you can safely delete the table: 37 | # sudo ipset destroy test 38 | ipset=/fb.me/test 39 | ipset=/:.*google.*:/test 40 | 41 | # nftables examples 42 | # 43 | # Tips: You need to manually create nftables ip set 'google-ipset' first: 44 | # sudo nft add table dnsmasq-table 45 | # sudo nft add set ip dnsmasq-table google-ipset '{ type ipv4_addr ; }' 46 | # sudo nft add element ip dnsmasq-table google-ipset '{ 8.8.8.8 }' 47 | # 48 | # and run dnsmasq with root(to modify this set), review the set: 49 | # sudo nft list ruleset 50 | # 51 | # after test, you can safely delete the table: 52 | # sudo nft delete table dnsmasq-table 53 | nftset=/googlevideo.com/ip#dnsmasq-table#google-ipset 54 | nftset=/:.*youtube.*:/ip#dnsmasq-table#google-ipset 55 | -------------------------------------------------------------------------------- /test/nslookup_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # for debug only 4 | # ./src/dnsmasq -d -C /tmp/dnsmasq_regex_example.conf -q 5 | 6 | DIG="dig @localhost -p30000 +retry=0" 7 | REDIR=/dev/null 8 | 9 | trap "exit" INT 10 | 11 | function printExit() 12 | { 13 | echo "Failed, exited" 14 | exit 1 15 | } 16 | 17 | echo 'UDP, server' 18 | domains=("jd.com" "www.fb.me" "fb.me" "github.com" "www.github.com" "mail.163.com") 19 | expect_dns=("114.114.114.114" "8.8.8.8" "8.8.8.8" "8.8.8.8" "8.8.8.8" "223.5.5.5") 20 | arraylength=${#domains[@]} 21 | for (( i=0; i<${arraylength}; i++ ));do 22 | domain=${domains[$i]} 23 | echo " $domain is forwared to ${expect_dns[$i]}" 24 | $DIG $domain > $REDIR || printExit 25 | done 26 | 27 | echo 'UDP, local' 28 | domains=("qq.com" "www.qq.com" "localhost.com" "www.localhost.com") 29 | expect_dns=("114.114.114.114" "local" "local" "local") 30 | arraylength=${#domains[@]} 31 | for (( i=0; i<${arraylength}; i++ ));do 32 | domain=${domains[$i]} 33 | echo " $domain is forwared to ${expect_dns[$i]}" 34 | $DIG $domain > $REDIR || printExit 35 | done 36 | 37 | echo 'TCP, server' 38 | domains=("2.taobao.com" "api.github.com" "163.com") 39 | expect_dns=("114.114.114.114" "8.8.8.8" "223.5.5.5") 40 | arraylength=${#domains[@]} 41 | for (( i=0; i<${arraylength}; i++ ));do 42 | domain=${domains[$i]} 43 | echo " $domain is forwared to ${expect_dns[$i]}" 44 | $DIG $domain +tcp > $REDIR || printExit 45 | done 46 | 47 | echo 'TCP, local' 48 | domains=("q.com" "1.q.com" "qzone.qq.com" "xxx.localhost.com") 49 | expect_dns=("114.114.114.114" "local" "local" "local") 50 | arraylength=${#domains[@]} 51 | for (( i=0; i<${arraylength}; i++ ));do 52 | domain=${domains[$i]} 53 | echo " $domain is forwared to ${expect_dns[$i]}" 54 | $DIG $domain +tcp > $REDIR || printExit 55 | done 56 | -------------------------------------------------------------------------------- /patches/002-regex-ipset.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/config.h b/src/config.h 2 | index 82024df..a606732 100644 3 | --- a/src/config.h 4 | +++ b/src/config.h 5 | @@ -200,6 +200,7 @@ RESOLVFILE 6 | /* #define HAVE_DNSSEC */ 7 | /* #define HAVE_NFTSET */ 8 | /* #define HAVE_REGEX */ 9 | +/* #define HAVE_REGEX_IPSET */ 10 | 11 | /* Default locations for important system files. */ 12 | 13 | @@ -397,7 +398,12 @@ static char *compile_opts = 14 | #ifndef HAVE_REGEX 15 | "no-" 16 | #endif 17 | -"regex " 18 | +"regex" 19 | +#if defined(HAVE_REGEX) && defined(HAVE_REGEX_IPSET) && (defined(HAVE_IPSET) || defined(HAVE_NFTSET)) 20 | +"(+ipset,nftset) " 21 | +#else 22 | +" " 23 | +#endif 24 | #if defined(HAVE_LIBIDN2) 25 | "IDN2 " 26 | #else 27 | diff --git a/src/dnsmasq.h b/src/dnsmasq.h 28 | index 3c7d0ee..49623e2 100644 29 | --- a/src/dnsmasq.h 30 | +++ b/src/dnsmasq.h 31 | @@ -661,6 +661,10 @@ struct ipsets { 32 | char **sets; 33 | char *domain; 34 | struct ipsets *next; 35 | +#if defined(HAVE_REGEX) && defined(HAVE_REGEX_IPSET) 36 | + pcre *regex; 37 | + pcre_extra *pextra; 38 | +#endif 39 | }; 40 | 41 | struct allowlist { 42 | diff --git a/src/forward.c b/src/forward.c 43 | index e8a57f4..d452ee8 100644 44 | --- a/src/forward.c 45 | +++ b/src/forward.c 46 | @@ -700,6 +700,12 @@ static struct ipsets *domain_find_sets(struct ipsets *setlist, const char *domai 47 | unsigned int matchlen = 0; 48 | for (ipset_pos = setlist; ipset_pos; ipset_pos = ipset_pos->next) 49 | { 50 | +#if defined(HAVE_REGEX) && defined(HAVE_REGEX_IPSET) 51 | + if (ipset_pos->regex){ 52 | + if (match_regex(ipset_pos->regex, ipset_pos->pextra, daemon->namebuff, namelen)) 53 | + ret = ipset_pos; 54 | + }else{ 55 | +#endif 56 | unsigned int domainlen = strlen(ipset_pos->domain); 57 | const char *matchstart = domain + namelen - domainlen; 58 | if (namelen >= domainlen && hostname_isequal(matchstart, ipset_pos->domain) && 59 | @@ -709,6 +715,9 @@ static struct ipsets *domain_find_sets(struct ipsets *setlist, const char *domai 60 | matchlen = domainlen; 61 | ret = ipset_pos; 62 | } 63 | +#if defined(HAVE_REGEX) && defined(HAVE_REGEX_IPSET) 64 | + } 65 | +#endif 66 | } 67 | 68 | return ret; 69 | diff --git a/src/option.c b/src/option.c 70 | index 07d66ae..924b2e5 100644 71 | --- a/src/option.c 72 | +++ b/src/option.c 73 | @@ -3197,6 +3197,18 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma 74 | while ((end = split_chr(arg, '/'))) 75 | { 76 | char *domain = NULL; 77 | +#if defined(HAVE_REGEX) && defined(HAVE_REGEX_IPSET) 78 | + char *real_end = arg + strlen(arg); 79 | + if (*arg == ':' && *(real_end - 1) == ':'){ 80 | + const char *error = NULL; 81 | + *(real_end - 1) = '\0'; 82 | + ipsets->next = opt_malloc(sizeof(struct ipsets)); 83 | + ipsets = ipsets->next; 84 | + memset(ipsets, 0, sizeof(struct ipsets)); 85 | + if ((error = parse_regex_option(arg + 1, &ipsets->regex, &ipsets->pextra))) 86 | + ret_err(error); 87 | + }else{ 88 | +#endif 89 | /* elide leading dots - they are implied in the search algorithm */ 90 | while (*arg == '.') 91 | arg++; 92 | @@ -3209,6 +3221,9 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma 93 | ipsets = ipsets->next; 94 | memset(ipsets, 0, sizeof(struct ipsets)); 95 | ipsets->domain = domain; 96 | +#if defined(HAVE_REGEX) && defined(HAVE_REGEX_IPSET) 97 | + } 98 | +#endif 99 | arg = end; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Dnsmasq with regex support 2 | 3 | Lastest version: v2.91 4 | 5 | patches: 6 | - [001-regex-server.patch](/patches/001-regex-server.patch) 7 | - [002-regex-ipset.patch](/patches/002-regex-ipset.patch) 8 | 9 | Inspired by these repos: 10 | - [dnsmasq-regexp_2.76](https://github.com/spacedingo/dnsmasq-regexp_2.76) 11 | - [dnsmasq-regex](https://github.com/cuckoohello/dnsmasq-regex) 12 | 13 | Original regex patch for dnsmasq 2.63 14 | - [using regular expressions in server list](http://lists.thekelleys.org.uk/pipermail/dnsmasq-discuss/2013q2/007124.html) 15 | - [dnsmasq-2.63-regex.patch](http://lists.thekelleys.org.uk/pipermail/dnsmasq-discuss/attachments/20130428/b3fc0de0/attachment.obj) 16 | 17 | Offical dnsmasq: 18 | - [dnsmasq](http://www.thekelleys.org.uk/dnsmasq/) 19 | 20 | ## Compile 21 | 22 | For Debian/Ubuntu: 23 | 24 | ``` 25 | # Install the dependencies 26 | sudo apt install -y libpcre3-dev libnftables-dev pkg-config 27 | 28 | # Clone the repo 29 | git clone https://github.com/lixingcong/dnsmasq-regex 30 | cd dnsmasq-regex 31 | 32 | # update the sub-module 'dnsmasq' to latest version 33 | # only update when a newer version is released 34 | bash ./update_submodule.sh 35 | 36 | # build it 37 | make 38 | 39 | # Run the binary, check if the compile option contains "regex(+ipset,nftset)" 40 | ./dnsmasq/src/dnsmasq --version 41 | ``` 42 | 43 | *Tips:* If you do not need the patch of ipset/nftables, just edit the file "Makefile" and build from source again. 44 | 45 | Change this line 46 | 47 | ``` 48 | DNSMASQ_COPTS="-DHAVE_REGEX -DHAVE_REGEX_IPSET" 49 | ``` 50 | 51 | to 52 | 53 | ``` 54 | DNSMASQ_COPTS="-DHAVE_REGEX" 55 | ``` 56 | 57 | ## Config file example 58 | 59 | You could write regex line starts with ':' and ends with ':' 60 | 61 | ``` 62 | server=114.114.114.114 63 | server=/google.com/8.8.8.8 64 | server=/:myvpn[0-9]*\.company\.com:/1.1.1.1 65 | server=/:a[0-9]\.yyy\.com:/# 66 | address=/:a[0-9]\.xxx\.com:/127.0.0.1 67 | ipset=/:.*youtube.*:/test 68 | nftset=/:.*\.google.co.*:/ip#dnsmasq-table#google-ipset 69 | ``` 70 | 71 | The config above will: 72 | 73 | - set default upstream server to ```114.114.114.114``` 74 | - match normal domain ```google.com``` then forward DNS queries to ```8.8.8.8``` 75 | - match domain ```myvpn[0-9]*\.company\.com``` then forward DNS queries to ```1.1.1.1``` 76 | - match domain ```a[0-9]\.yyy\.com``` then forward DNS queries ```114.114.114.114``` normally(default upstream server) 77 | - match domain ```a[0-9]\.xxx\.com``` then return DNS record of localhost(to block ads?) 78 | - add ```.*youtube.*``` query answers to ipset ```test``` 79 | - add ```.*\.google.co.*``` query answers to nftables set, equivalent to ```nft add element ip dnsmasq-table google-ipset { 172.217.161.74 }``` 80 | 81 | Here is a example config file: [dnsmasq\_regex\_example.conf](/dnsmasq_regex_example.conf) 82 | 83 | Tips: 84 | 85 | - A simple script to generate domains configurations: [my-gfwlist](https://github.com/lixingcong/my-gfwlist) 86 | 87 | - The regex line ```[a-z]*gle\.com``` will match both ```google.com``` and ```google.com.hk```. Use anchor ```^``` and ```$``` to produce a more precise match. 88 | 89 | ### Notes for version >= v2.86 90 | 91 | Simon, the author of Dnsmasq, has [rewritten](https://thekelleys.org.uk/gitweb/?p=dnsmasq.git;a=commit;h=12a9aa7c628e2d7dcd34949603848a3fb53fce9c) the function to shorten the lookup time for queries. I have to rewrite the patch too. So the domain match function was changed. 92 | 93 | If you upgrade from older version(2.85 or older), considering modify your config file. Maybe just simply move lines up and down.😉 94 | 95 | The regex lines will generate a linkedlist to match(from top to bottom). If the domain matched both regex servers, DNS query will be forwarded the one which appears first. 96 | 97 | Consider the config file below, the domain ```wx.qq.com``` will be forwarded to upstream ```1.1.1.1```, not ```8.8.8.8``` 98 | 99 | ``` 100 | server=/:\.qq\.com:/1.1.1.1 101 | server=/:\.qq\.com:/8.8.8.8 102 | ``` 103 | 104 | If the domain matched normal and regex servers, DNS query will be forwarded to the normal one. 105 | 106 | Consider the config file below, the domain ```wx.qq.com``` will be forwarded to upstream ```1.1.1.1```, neither ```8.8.8.8``` nor ```1.2.4.8``` 107 | 108 | ``` 109 | server=/:w\w?\.qq\.com:/1.2.4.8 110 | server=/qq.com/1.1.1.1 111 | server=/:\.qq\.com:/8.8.8.8 112 | ``` 113 | 114 | ## OpenWrt/LEDE package 115 | 116 | Please check this page: [dnsmasq-regex-openwrt](https://github.com/lixingcong/dnsmasq-regex-openwrt) 117 | -------------------------------------------------------------------------------- /patches/001-regex-server.patch: -------------------------------------------------------------------------------- 1 | diff --git a/Makefile b/Makefile 2 | index a503c82..37e92f2 100644 3 | --- a/Makefile 4 | +++ b/Makefile 5 | @@ -24,7 +24,11 @@ MANDIR = $(PREFIX)/share/man 6 | LOCALEDIR = $(PREFIX)/share/locale 7 | BUILDDIR = $(SRC) 8 | DESTDIR = 9 | +ifndef BUILD_DEBUG 10 | CFLAGS = -Wall -W -O2 11 | +else 12 | +CFLAGS = -Wall -W -g 13 | +endif 14 | LDFLAGS = 15 | COPTS = 16 | RPM_OPT_FLAGS = 17 | @@ -66,6 +70,8 @@ lua_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_LUASCRIPT $(PKG_CON 18 | nettle_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DNSSEC $(PKG_CONFIG) --cflags 'nettle hogweed'` 19 | nettle_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DNSSEC $(PKG_CONFIG) --libs 'nettle hogweed'` 20 | gmp_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DNSSEC NO_GMP --copy -lgmp` 21 | +regex_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_REGEX $(PKG_CONFIG) --cflags libpcre` 22 | +regex_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_REGEX $(PKG_CONFIG) --libs libpcre` 23 | sunos_libs = `if uname | grep SunOS >/dev/null 2>&1; then echo -lsocket -lnsl -lposix4; fi` 24 | nft_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_NFTSET $(PKG_CONFIG) --cflags libnftables` 25 | nft_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_NFTSET $(PKG_CONFIG) --libs libnftables` 26 | @@ -89,8 +95,8 @@ hdrs = dnsmasq.h config.h dhcp-protocol.h dhcp6-protocol.h \ 27 | all : $(BUILDDIR) 28 | @cd $(BUILDDIR) && $(MAKE) \ 29 | top="$(top)" \ 30 | - build_cflags="$(version) $(dbus_cflags) $(idn2_cflags) $(idn_cflags) $(ct_cflags) $(lua_cflags) $(nettle_cflags) $(nft_cflags)" \ 31 | - build_libs="$(dbus_libs) $(idn2_libs) $(idn_libs) $(ct_libs) $(lua_libs) $(sunos_libs) $(nettle_libs) $(gmp_libs) $(ubus_libs) $(nft_libs)" \ 32 | + build_cflags="$(version) $(dbus_cflags) $(idn2_cflags) $(idn_cflags) $(ct_cflags) $(lua_cflags) $(nettle_cflags) $(nft_cflags) $(regex_cflags)" \ 33 | + build_libs="$(dbus_libs) $(idn2_libs) $(idn_libs) $(ct_libs) $(lua_libs) $(sunos_libs) $(nettle_libs) $(gmp_libs) $(ubus_libs) $(nft_libs) $(regex_libs)" \ 34 | -f $(top)/Makefile dnsmasq 35 | 36 | mostly_clean : 37 | @@ -112,8 +118,8 @@ all-i18n : $(BUILDDIR) 38 | @cd $(BUILDDIR) && $(MAKE) \ 39 | top="$(top)" \ 40 | i18n=-DLOCALEDIR=\'\"$(LOCALEDIR)\"\' \ 41 | - build_cflags="$(version) $(dbus_cflags) $(idn2_cflags) $(idn_cflags) $(ct_cflags) $(lua_cflags) $(nettle_cflags) $(nft_cflags)" \ 42 | - build_libs="$(dbus_libs) $(idn2_libs) $(idn_libs) $(ct_libs) $(lua_libs) $(sunos_libs) $(nettle_libs) $(gmp_libs) $(ubus_libs) $(nft_libs)" \ 43 | + build_cflags="$(version) $(dbus_cflags) $(idn2_cflags) $(idn_cflags) $(ct_cflags) $(lua_cflags) $(nettle_cflags) $(nft_cflags) $(regex_cflags)" \ 44 | + build_libs="$(dbus_libs) $(idn2_libs) $(idn_libs) $(ct_libs) $(lua_libs) $(sunos_libs) $(nettle_libs) $(gmp_libs) $(ubus_libs) $(nft_libs) $(regex_libs)" \ 45 | -f $(top)/Makefile dnsmasq 46 | for f in `cd $(PO); echo *.po`; do \ 47 | cd $(top) && cd $(BUILDDIR) && $(MAKE) top="$(top)" -f $(top)/Makefile $${f%.po}.mo; \ 48 | diff --git a/src/config.h b/src/config.h 49 | index 1f1eae2..82024df 100644 50 | --- a/src/config.h 51 | +++ b/src/config.h 52 | @@ -199,6 +199,7 @@ RESOLVFILE 53 | /* #define HAVE_CONNTRACK */ 54 | /* #define HAVE_DNSSEC */ 55 | /* #define HAVE_NFTSET */ 56 | +/* #define HAVE_REGEX */ 57 | 58 | /* Default locations for important system files. */ 59 | 60 | @@ -393,6 +394,10 @@ static char *compile_opts = 61 | "no-" 62 | #endif 63 | "i18n " 64 | +#ifndef HAVE_REGEX 65 | +"no-" 66 | +#endif 67 | +"regex " 68 | #if defined(HAVE_LIBIDN2) 69 | "IDN2 " 70 | #else 71 | diff --git a/src/dnsmasq.h b/src/dnsmasq.h 72 | index 59d1dfa..3c7d0ee 100644 73 | --- a/src/dnsmasq.h 74 | +++ b/src/dnsmasq.h 75 | @@ -162,6 +162,10 @@ extern int capget(cap_user_header_t header, cap_user_data_t data); 76 | /* daemon is function in the C library.... */ 77 | #define daemon dnsmasq_daemon 78 | 79 | +#ifdef HAVE_REGEX 80 | +#include 81 | +#endif 82 | + 83 | #define ADDRSTRLEN INET6_ADDRSTRLEN 84 | 85 | /* Async event queue */ 86 | @@ -594,6 +598,10 @@ struct randfd_list { 87 | struct server { 88 | u16 flags, domain_len; 89 | char *domain; 90 | +#ifdef HAVE_REGEX 91 | + pcre *regex; 92 | + pcre_extra *pextra; 93 | +#endif 94 | struct server *next; 95 | int serial, arrayposn; 96 | int last_server; 97 | @@ -615,6 +623,10 @@ struct server { 98 | struct serv_addr4 { 99 | u16 flags, domain_len; 100 | char *domain; 101 | +#ifdef HAVE_REGEX 102 | + pcre *regex; 103 | + pcre_extra *pextra; 104 | +#endif 105 | struct server *next; 106 | struct in_addr addr; 107 | }; 108 | @@ -622,6 +634,10 @@ struct serv_addr4 { 109 | struct serv_addr6 { 110 | u16 flags, domain_len; 111 | char *domain; 112 | +#ifdef HAVE_REGEX 113 | + pcre *regex; 114 | + pcre_extra *pextra; 115 | +#endif 116 | struct server *next; 117 | struct in6_addr addr; 118 | }; 119 | @@ -629,6 +645,10 @@ struct serv_addr6 { 120 | struct serv_local { 121 | u16 flags, domain_len; 122 | char *domain; 123 | +#ifdef HAVE_REGEX 124 | + pcre *regex; 125 | + pcre_extra *pextra; 126 | +#endif 127 | struct server *next; 128 | }; 129 | 130 | @@ -1182,6 +1202,9 @@ extern struct daemon { 131 | struct rebind_domain *no_rebind; 132 | int server_has_wildcard; 133 | int serverarraysz, serverarrayhwm; 134 | +#ifdef HAVE_REGEX 135 | + int regexserverarraysz, regexlocaldomainarraysz; 136 | +#endif 137 | struct ipsets *ipsets, *nftsets; 138 | u32 allowlist_mask; 139 | struct allowlist *allowlists; 140 | @@ -1905,6 +1928,12 @@ void dump_packet_icmp(int mask, void *packet, size_t len, union mysockaddr *src, 141 | #endif 142 | 143 | /* domain-match.c */ 144 | +#ifdef HAVE_REGEX 145 | +int is_local_regex_answer(const char *domain, int *first, int *last); 146 | +int find_regex_server(const char* domain, int is_local, int *arraypos); 147 | +int match_regex(const pcre *regex, const pcre_extra *pextra, const char *str, size_t len); 148 | +const char *parse_regex_option(const char *arg, pcre **regex, pcre_extra **pextra); 149 | +#endif 150 | void build_server_array(void); 151 | int lookup_domain(char *qdomain, int flags, int *lowout, int *highout); 152 | int filter_servers(int seed, int flags, int *lowout, int *highout); 153 | diff --git a/src/domain-match.c b/src/domain-match.c 154 | index 80a3602..1d17f79 100644 155 | --- a/src/domain-match.c 156 | +++ b/src/domain-match.c 157 | @@ -27,47 +27,90 @@ void build_server_array(void) 158 | { 159 | struct server *serv; 160 | int count = 0; 161 | +#ifdef HAVE_REGEX 162 | + int regexserverarrayidx = 0; 163 | + int regexlocaldomainarrayidx = 0; 164 | + int regexserverarraysz = 0; 165 | + int regexlocaldomainarraysz = 0; 166 | +#endif 167 | 168 | - for (serv = daemon->servers; serv; serv = serv->next) 169 | + for (serv = daemon->servers; serv; serv = serv->next){ 170 | #ifdef HAVE_LOOP 171 | if (!(serv->flags & SERV_LOOP)) 172 | +#endif 173 | + { 174 | +#ifdef HAVE_REGEX 175 | + if(serv->regex) 176 | + ++regexserverarraysz; 177 | + else 178 | #endif 179 | { 180 | count++; 181 | if (serv->flags & SERV_WILDCARD) 182 | daemon->server_has_wildcard = 1; 183 | } 184 | + } 185 | + } 186 | 187 | - for (serv = daemon->local_domains; serv; serv = serv->next) 188 | + for (serv = daemon->local_domains; serv; serv = serv->next){ 189 | +#ifdef HAVE_REGEX 190 | + if(serv->regex) 191 | + ++regexlocaldomainarraysz; 192 | + else 193 | +#endif 194 | { 195 | count++; 196 | if (serv->flags & SERV_WILDCARD) 197 | daemon->server_has_wildcard = 1; 198 | } 199 | + } 200 | 201 | daemon->serverarraysz = count; 202 | 203 | +#ifdef HAVE_REGEX 204 | + if (count > daemon->serverarrayhwm || (regexserverarraysz + regexlocaldomainarraysz) > (daemon->regexserverarraysz + daemon->regexlocaldomainarraysz)) 205 | +#else 206 | if (count > daemon->serverarrayhwm) 207 | +#endif 208 | { 209 | struct server **new; 210 | 211 | count += 10; /* A few extra without re-allocating. */ 212 | 213 | +#ifdef HAVE_REGEX 214 | + if ((new = whine_malloc((count + regexserverarraysz + regexlocaldomainarraysz) * sizeof(struct server *)))) 215 | +#else 216 | if ((new = whine_malloc(count * sizeof(struct server *)))) 217 | +#endif 218 | { 219 | if (daemon->serverarray) 220 | free(daemon->serverarray); 221 | 222 | daemon->serverarray = new; 223 | daemon->serverarrayhwm = count; 224 | +#ifdef HAVE_REGEX 225 | + daemon->regexserverarraysz = regexserverarraysz; 226 | + daemon->regexlocaldomainarraysz = regexlocaldomainarraysz; 227 | +#endif 228 | } 229 | } 230 | 231 | count = 0; 232 | +#ifdef HAVE_REGEX 233 | + regexserverarrayidx = daemon->serverarrayhwm; 234 | + regexlocaldomainarrayidx = regexserverarrayidx + daemon->regexserverarraysz; 235 | +#endif 236 | 237 | - for (serv = daemon->servers; serv; serv = serv->next) 238 | + for (serv = daemon->servers; serv; serv = serv->next){ 239 | #ifdef HAVE_LOOP 240 | if (!(serv->flags & SERV_LOOP)) 241 | +#endif 242 | + { 243 | +#ifdef HAVE_REGEX 244 | + if(serv->regex){ 245 | + daemon->serverarray[regexserverarrayidx++]=serv; 246 | + continue; 247 | + }else 248 | #endif 249 | { 250 | daemon->serverarray[count] = serv; 251 | @@ -75,9 +118,18 @@ void build_server_array(void) 252 | serv->last_server = -1; 253 | count++; 254 | } 255 | - 256 | - for (serv = daemon->local_domains; serv; serv = serv->next, count++) 257 | - daemon->serverarray[count] = serv; 258 | + } 259 | + } 260 | + 261 | + for (serv = daemon->local_domains; serv; serv = serv->next){ 262 | +#ifdef HAVE_REGEX 263 | + if(serv->regex){ 264 | + daemon->serverarray[regexlocaldomainarrayidx++]=serv; 265 | + continue; 266 | + }else 267 | +#endif 268 | + daemon->serverarray[count++] = serv; 269 | + } 270 | 271 | qsort(daemon->serverarray, daemon->serverarraysz, sizeof(struct server *), order_qsort); 272 | 273 | @@ -86,6 +138,20 @@ void build_server_array(void) 274 | for (count = 0; count < daemon->serverarraysz; count++) 275 | if (!(daemon->serverarray[count]->flags & SERV_IS_LOCAL)) 276 | daemon->serverarray[count]->arrayposn = count; 277 | + 278 | +#ifdef HAVE_REGEX 279 | + for (count = daemon->serverarrayhwm; count < daemon->serverarrayhwm + daemon->regexserverarraysz; ++count) 280 | + daemon->serverarray[count]->arrayposn = count; 281 | +#endif 282 | + 283 | +#if 0 // print the whole array, for debug only 284 | + for(count=0; count < daemon->serverarrayhwm + daemon->regexserverarraysz + daemon->regexlocaldomainarraysz; ++count){ 285 | + struct server* sv= daemon->serverarray[count]; 286 | + if(sv) 287 | + printf("i = %d, flag=%u, p=%p, nextP=%p, domain = %s, sfd=%p\n", 288 | + count, sv->flags, sv, sv->next, sv->domain, sv->sfd); 289 | + } 290 | +#endif 291 | } 292 | 293 | /* we're looking for the server whose domain is the longest exact match 294 | @@ -103,6 +169,11 @@ void build_server_array(void) 295 | */ 296 | int lookup_domain(char *domain, int flags, int *lowout, int *highout) 297 | { 298 | +#ifdef HAVE_REGEX 299 | + int needsearchregex = 1; 300 | + const char* originaldomain = domain; 301 | +#endif 302 | + int founddomain = 0; 303 | int rc, crop_query, nodots; 304 | ssize_t qlen; 305 | int try, high, low = 0; 306 | @@ -111,7 +182,7 @@ int lookup_domain(char *domain, int flags, int *lowout, int *highout) 307 | 308 | /* may be no configured servers. */ 309 | if (daemon->serverarraysz == 0) 310 | - return 0; 311 | + goto search_regex; 312 | 313 | /* find query length and presence of '.' */ 314 | for (cp = qdomain, nodots = 1, qlen = 0; *cp; qlen++, cp++) 315 | @@ -255,9 +326,42 @@ int lookup_domain(char *domain, int flags, int *lowout, int *highout) 316 | 317 | /* qlen == -1 when we failed to match even an empty query, if there are no default servers. */ 318 | if (nlow == nhigh || qlen == -1) 319 | - return 0; 320 | - 321 | - return 1; 322 | + goto search_regex; 323 | + 324 | + founddomain = 1; 325 | + 326 | +search_regex: 327 | +#ifdef HAVE_REGEX 328 | + if (founddomain){ 329 | + if (daemon->serverarray[nlow]->domain_len > 0) // have found a valid upstream 330 | + needsearchregex = 0; 331 | + } 332 | + 333 | + if (needsearchregex){ 334 | + int found_regex = find_regex_server(originaldomain, 0, &low); 335 | + 336 | + if(!found_regex && find_regex_server(originaldomain, 1, &low)){ 337 | + found_regex=1; 338 | + 339 | + // special step for parse "server=/:xxx:/#" 340 | + if(daemon->serverarray[low]->flags & SERV_USE_RESOLV){ 341 | + if(filter_servers(try, F_SERVER, &nlow, &nhigh)){ 342 | + low=nlow; 343 | + } 344 | + } 345 | + } 346 | + 347 | + if(found_regex){ 348 | + if (lowout) 349 | + *lowout = low; 350 | + if (highout) 351 | + *highout = low + 1; 352 | + 353 | + founddomain = 1; 354 | + } 355 | + } 356 | +#endif 357 | + return founddomain; 358 | } 359 | 360 | /* Return first server in group of equivalent servers; this is the "master" record. */ 361 | @@ -270,6 +374,14 @@ int filter_servers(int seed, int flags, int *lowout, int *highout) 362 | { 363 | int nlow = seed, nhigh = seed; 364 | int i; 365 | + 366 | +#ifdef HAVE_REGEX 367 | + if(nlow >= daemon->serverarrayhwm){ 368 | + *lowout = nlow; 369 | + *highout = nlow+1; 370 | + return 1; 371 | + } 372 | +#endif 373 | 374 | /* expand nlow and nhigh to cover all the records with the same domain 375 | nlow is the first, nhigh - 1 is the last. nlow=nhigh means no servers, 376 | @@ -366,6 +478,89 @@ int filter_servers(int seed, int flags, int *lowout, int *highout) 377 | return (nlow != nhigh); 378 | } 379 | 380 | +#ifdef HAVE_REGEX 381 | +// return flags, or 0 if not found 382 | +// if argument domain is NULL, check the 'first' server is local answer, make sure 'first' is valid 383 | +int is_local_regex_answer(const char *domain, int *first, int *last) 384 | +{ 385 | + int flags = 0; 386 | + int rc = 0; 387 | + int arraypos = 0; 388 | + int found = 1; 389 | + 390 | + if(domain){ 391 | + found = find_regex_server(domain, 1, &arraypos); 392 | + if(found){ 393 | + *first = arraypos; 394 | + *last = *first + 1; 395 | + } 396 | + }else 397 | + arraypos = *first; 398 | + 399 | + if(found){ 400 | + struct server *r = daemon->serverarray[arraypos]; 401 | + 402 | + flags = r->flags; 403 | + if (flags & SERV_4ADDR) 404 | + rc = F_IPV4; 405 | + else if (flags & SERV_6ADDR) 406 | + rc = F_IPV6; 407 | + else if (flags & SERV_ALL_ZEROS) 408 | + rc = F_IPV4 | F_IPV6; 409 | + } 410 | + return rc; 411 | +} 412 | + 413 | +// return 0 if failed to find 414 | +int find_regex_server(const char* domain, int is_local, int *arraypos) 415 | +{ 416 | + int iFirst = daemon->serverarrayhwm; 417 | + int iLast = daemon->serverarrayhwm + daemon->regexserverarraysz; 418 | + const size_t domainLength = strlen(domain); 419 | + 420 | + if (is_local){ 421 | + iFirst = iLast; 422 | + iLast += daemon->regexlocaldomainarraysz; 423 | + } 424 | + 425 | + while(iFirst < iLast){ 426 | + struct server* r = daemon->serverarray[iFirst]; 427 | + if (match_regex(r->regex, r->pextra, domain, domainLength)){ 428 | + *arraypos=iFirst; 429 | + return 1; 430 | + } 431 | + ++iFirst; 432 | + } 433 | + 434 | + return 0; 435 | +} 436 | + 437 | +// return 0 if failed to match 438 | +int match_regex(const pcre *regex, const pcre_extra *pextra, const char *str, size_t len) 439 | +{ 440 | + int captcount = 0; 441 | + int ret = 0; 442 | + if (pcre_fullinfo(regex, pextra, PCRE_INFO_CAPTURECOUNT, &captcount) == 0) 443 | + { 444 | + /* C99 dyn-array, or alloca must be used */ 445 | + int ovect[(captcount + 1) * 3]; 446 | + ret = pcre_exec(regex, pextra, str, len, 0, 0, ovect, (captcount + 1) * 3) > 0; 447 | + } 448 | + return ret; 449 | +} 450 | + 451 | +const char *parse_regex_option(const char *arg, pcre **regex, pcre_extra **pextra) 452 | +{ 453 | + const char *error; 454 | + int erroff; 455 | + *regex = pcre_compile(arg, 0, &error, &erroff, NULL); 456 | + if(NULL == *regex) 457 | + return error; 458 | + *pextra = pcre_study(*regex, 0, &error); 459 | + return NULL; 460 | +} 461 | +#endif 462 | + 463 | int is_local_answer(time_t now, int first, char *name) 464 | { 465 | int flags = 0; 466 | @@ -519,6 +714,9 @@ static int order(char *qdomain, size_t qlen, struct server *serv) 467 | static int order_servers(struct server *s1, struct server *s2) 468 | { 469 | int rc; 470 | +#ifdef HAVE_REGEX 471 | + if(!s1) return -1; 472 | +#endif 473 | 474 | /* need full comparison of dotless servers in 475 | order_qsort() and filter_servers() */ 476 | @@ -640,6 +838,9 @@ int add_update_server(int flags, 477 | const char *domain, 478 | union all_addr *local_addr) 479 | { 480 | +#ifdef HAVE_REGEX 481 | + const char* regex = NULL; 482 | +#endif 483 | struct server *serv = NULL; 484 | char *alloc_domain; 485 | 486 | @@ -655,10 +856,32 @@ int add_update_server(int flags, 487 | if (*domain != 0) 488 | flags |= SERV_WILDCARD; 489 | } 490 | - 491 | +#ifdef HAVE_REGEX 492 | + else{ 493 | + size_t domainLen=strlen(domain); 494 | + char* regex_end=(char*)domain+domainLen-1; 495 | + if (domainLen > 2 && *domain == ':' && *regex_end == ':'){ 496 | + const char* err = NULL; 497 | + 498 | + ++domain; // skip leading ':' 499 | + --domainLen; // skip leading ':' 500 | + 501 | + *regex_end = '\0'; // skip tailing ':' 502 | + regex = domain; 503 | + 504 | + // call whine_malloc() instead of canonicalise() 505 | + if ((alloc_domain = whine_malloc(domainLen))) 506 | + memcpy(alloc_domain, domain, domainLen); 507 | + } 508 | + } 509 | +#endif 510 | + 511 | if (*domain == 0) 512 | alloc_domain = whine_malloc(1); 513 | else 514 | +#ifdef HAVE_REGEX 515 | + if(!regex) 516 | +#endif 517 | alloc_domain = canonicalise((char *)domain, NULL); 518 | 519 | if (!alloc_domain) 520 | @@ -760,6 +983,16 @@ int add_update_server(int flags, 521 | serv->flags = flags; 522 | serv->domain = alloc_domain; 523 | serv->domain_len = strlen(alloc_domain); 524 | + 525 | +#ifdef HAVE_REGEX 526 | + if (regex){ 527 | + const char* err = NULL; 528 | + if ((err = (char *)parse_regex_option(regex, &serv->regex, &serv->pextra))){ 529 | + printf("parse_regex_option: %s\n", err); 530 | + return 0; 531 | + } 532 | + } 533 | +#endif 534 | 535 | return 1; 536 | } 537 | diff --git a/src/forward.c b/src/forward.c 538 | index 28b6ffd..e8a57f4 100644 539 | --- a/src/forward.c 540 | +++ b/src/forward.c 541 | @@ -396,6 +396,11 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr, 542 | p = (unsigned char *)(header+1); 543 | if (!extract_name(header, plen, &p, (char *)&forward->frec_src.encode_bitmap, EXTR_NAME_FLIP, 1)) 544 | goto reply; 545 | + 546 | +#ifdef HAVE_REGEX 547 | + if ((flags = is_local_regex_answer(NULL, &first, &last))) 548 | + goto reply; 549 | +#endif 550 | 551 | /* Keep copy of query for retries and move to TCP */ 552 | if (!(forward->stash = blockdata_alloc((char *)header, plen))) 553 | @@ -436,6 +441,9 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr, 554 | forward->forwardall = 1; 555 | } 556 | else 557 | +#ifdef HAVE_REGEX 558 | + if(!master->regex) 559 | +#endif 560 | start = master->last_server; 561 | } 562 | } 563 | @@ -476,7 +484,14 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr, 564 | forward->sentto->failed_queries++; 565 | else 566 | forward->sentto->retrys++; 567 | - 568 | + 569 | +#ifdef HAVE_REGEX 570 | + if(forward->sentto->regex){ 571 | + start = first = forward->sentto->arrayposn; 572 | + last = first + 1; 573 | + forward->forwardall = 0; 574 | + }else 575 | +#endif 576 | if (!filter_servers(forward->sentto->arrayposn, F_SERVER, &first, &last)) 577 | goto reply; 578 | 579 | @@ -526,8 +541,13 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr, 580 | while (1) 581 | { 582 | int fd; 583 | - struct server *srv = daemon->serverarray[start]; 584 | - 585 | + 586 | + struct server *srv = daemon->serverarray[start]; 587 | +#ifdef HAVE_REGEX 588 | + if (srv->regex) 589 | + forward->forwardall = 0; // make it send only once 590 | +#endif 591 | + 592 | if ((fd = allocate_rfd(&forward->rfds, srv)) != -1) 593 | { 594 | 595 | @@ -2576,8 +2596,17 @@ unsigned char *tcp_request(int confd, time_t now, 596 | checking_disabled = header->hb4 & HB4_CD; 597 | 598 | if (lookup_domain(daemon->namebuff, gotname, &first, &last)) 599 | + { 600 | flags = is_local_answer(now, first, daemon->namebuff); 601 | +#ifdef HAVE_REGEX 602 | + if(!flags) 603 | + flags = is_local_regex_answer(NULL, &first, &last); 604 | +#endif 605 | + } 606 | else 607 | +#ifdef HAVE_REGEX 608 | + if(!(flags = is_local_regex_answer(daemon->namebuff, &first, &last))) 609 | +#endif 610 | ede = EDE_NOT_READY; 611 | 612 | if (!flags && ede == EDE_UNSET) 613 | @@ -2592,7 +2621,11 @@ unsigned char *tcp_request(int confd, time_t now, 614 | { 615 | master = daemon->serverarray[first]; 616 | 617 | +#ifdef HAVE_REGEX 618 | + if (option_bool(OPT_ORDER) || master->last_server == -1 || master->regex) 619 | +#else 620 | if (option_bool(OPT_ORDER) || master->last_server == -1) 621 | +#endif 622 | start = first; 623 | else 624 | start = master->last_server; 625 | diff --git a/src/network.c b/src/network.c 626 | index 15a38fc..afde158 100644 627 | --- a/src/network.c 628 | +++ b/src/network.c 629 | @@ -1667,6 +1667,10 @@ void check_servers(int no_loop_check) 630 | s1 = _("unqualified"), s2 = _("names"); 631 | else if (strlen(serv->domain) == 0) 632 | s1 = _("default"), s2 = ""; 633 | +#ifdef HAVE_REGEX 634 | + else if (serv->regex) 635 | + s1 = _("regex domain"), s2 = serv->domain; 636 | +#endif 637 | else 638 | s1 = _("domain"), s2 = serv->domain, s4 = (serv->flags & SERV_WILDCARD) ? "*" : ""; 639 | 640 | @@ -1696,8 +1700,14 @@ void check_servers(int no_loop_check) 641 | if (++locals <= LOCALS_LOGGED) 642 | my_syslog(LOG_INFO, _("using only locally-known addresses for %s"), serv->domain); 643 | } 644 | - else if (serv->flags & SERV_USE_RESOLV) 645 | + else if (serv->flags & SERV_USE_RESOLV){ 646 | +#ifdef HAVE_REGEX 647 | + if (serv->regex) 648 | + my_syslog(LOG_INFO, _("using standard nameservers for regex domain %s"), serv->domain); 649 | + else 650 | +#endif 651 | my_syslog(LOG_INFO, _("using standard nameservers for %s"), serv->domain); 652 | + } 653 | } 654 | 655 | if (locals > LOCALS_LOGGED) 656 | --------------------------------------------------------------------------------