From d2621f55d243cdfebda89cdb09476a49cb0135ce Mon Sep 17 00:00:00 2001 From: Andrey Meshkov Date: Mon, 19 Oct 2020 17:20:49 +0300 Subject: [PATCH 1/3] Added DNSCrypt server implementation --- .codecov.yml | 2 +- .gitignore | 1 + .travis.yml | 88 ++++- Makefile | 43 +++ README.md | 167 +++++++-- build/dnscrypt-windows-386-dev.zip | Bin 0 -> 101369 bytes build/windows-386/LICENSE | 25 ++ build/windows-386/README.md | 144 ++++++++ build/windows-386/dnscrypt.exe | Bin 0 -> 360398 bytes cert.go | 176 +++++++++ cert_test.go | 78 ++++ client.go | 286 +++++++++++++++ client_test.go | 186 ++++++++++ cmd/generate.go | 50 +++ cmd/lookup.go | 110 ++++++ cmd/main.go | 51 +++ cmd/server.go | 111 ++++++ constants.go | 113 ++++++ dnscrypt.go | 551 ----------------------------- dnscrypt_test.go | 164 --------- doc.go | 72 +++- encrypted_query.go | 141 ++++++++ encrypted_query_test.go | 57 +++ encrypted_response.go | 106 ++++++ encrypted_response_test.go | 72 ++++ generate.go | 167 +++++++++ generate_test.go | 38 ++ go.mod | 6 +- go.sum | 20 ++ handler.go | 60 ++++ server.go | 141 ++++++++ server_tcp.go | 122 +++++++ server_test.go | 169 +++++++++ server_udp.go | 124 +++++++ udp_unix.go | 83 +++++ udp_windows.go | 30 ++ util.go | 251 +++++++++++++ 37 files changed, 3236 insertions(+), 769 deletions(-) create mode 100644 Makefile create mode 100644 build/dnscrypt-windows-386-dev.zip create mode 100644 build/windows-386/LICENSE create mode 100644 build/windows-386/README.md create mode 100644 build/windows-386/dnscrypt.exe create mode 100644 cert.go create mode 100644 cert_test.go create mode 100644 client.go create mode 100644 client_test.go create mode 100644 cmd/generate.go create mode 100644 cmd/lookup.go create mode 100644 cmd/main.go create mode 100644 cmd/server.go create mode 100644 constants.go delete mode 100644 dnscrypt.go delete mode 100644 dnscrypt_test.go create mode 100644 encrypted_query.go create mode 100644 encrypted_query_test.go create mode 100644 encrypted_response.go create mode 100644 encrypted_response_test.go create mode 100644 generate.go create mode 100644 generate_test.go create mode 100644 handler.go create mode 100644 server.go create mode 100644 server_tcp.go create mode 100644 server_test.go create mode 100644 server_udp.go create mode 100644 udp_unix.go create mode 100644 udp_windows.go create mode 100644 util.go diff --git a/.codecov.yml b/.codecov.yml index f91e5c1..1a01bc2 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -2,7 +2,7 @@ coverage: status: project: default: - target: 40% + target: 65% threshold: null patch: false changes: false diff --git a/.gitignore b/.gitignore index 706fd07..652d89d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .idea .vscode +coverage.txt \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index f87900a..9de7731 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,94 @@ language: go sudo: false +os: + - linux + - osx + - windows + env: -- GO111MODULE=on + - GO111MODULE=on go: -- 1.x + - 1.x + +before_install: + - |- + case $TRAVIS_OS_NAME in + linux | osx) + curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s -- -b $(go env GOPATH)/bin v1.31.0 + ;; + esac script: -- go test -race -v -bench=. -coverprofile=coverage.txt -covermode=atomic ./... + - |- + # Fail if any command fails + set -e + + case $TRAVIS_OS_NAME in + linux | osx) + # Run linter + golangci-lint run + + # Run tests + go test -race -v -bench=. -coverprofile=coverage.txt -covermode=atomic ./... + + # Windows-386 build + GOOS=windows GOARCH=386 VERSION=${TRAVIS_TAG:-dev} make release + + # Windows-amd64 build + GOOS=windows GOARCH=amd64 VERSION=${TRAVIS_TAG:-dev} make release + + # Linux-386 build + GOOS=linux GOARCH=386 VERSION=${TRAVIS_TAG:-dev} make release + + # Linux-amd64 build + GOOS=linux GOARCH=amd64 VERSION=${TRAVIS_TAG:-dev} make release + + # Linux-arm64 build + GOOS=linux GOARCH=arm64 VERSION=${TRAVIS_TAG:-dev} make release + + # Linux-armv6 build + GOOS=linux GOARCH=arm GOARM=6 VERSION=${TRAVIS_TAG:-dev} make release + + # Linux-mips build + GOOS=linux GOARCH=mips GOMIPS=softfloat VERSION=${TRAVIS_TAG:-dev} make release + + # Linux-mipsle build + GOOS=linux GOARCH=mipsle GOMIPS=softfloat VERSION=${TRAVIS_TAG:-dev} make release + + # freebsd-armv6 build + GOOS=freebsd GOARCH=arm GOARM=6 VERSION=${TRAVIS_TAG:-dev} make release + + # Darwin-amd64 build + GOOS=darwin GOARCH=amd64 VERSION=${TRAVIS_TAG:-dev} make release + + # List build output + ls -l build/dnscrypt-* + ;; + windows) + # Run tests + go test -race -v -bench=. -coverprofile=coverage.txt -covermode=atomic ./... + ;; + esac after_success: -- bash <(curl -s https://codecov.io/bash) + - |- + case $TRAVIS_OS_NAME in + linux) + bash <(curl -s https://codecov.io/bash) + ;; + esac + +deploy: + provider: releases + api_key: $GITHUB_TOKEN + file: + - build/dnscrypt-*.zip + - build/dnscrypt-*.tar.gz + on: + repo: ameshkov/dnscrypt + tags: true + condition: $TRAVIS_OS_NAME = linux + file_glob: true + skip_cleanup: true diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..77407bf --- /dev/null +++ b/Makefile @@ -0,0 +1,43 @@ +NAME=dnscrypt +BASE_BUILDDIR=build +BUILDNAME=$(GOOS)-$(GOARCH)$(GOARM) +BUILDDIR=$(BASE_BUILDDIR)/$(BUILDNAME) +VERSION?=dev + +ifeq ($(GOOS),windows) + ext=.exe + archiveCmd=zip -9 -r $(NAME)-$(BUILDNAME)-$(VERSION).zip $(BUILDNAME) +else + ext= + archiveCmd=tar czpvf $(NAME)-$(BUILDNAME)-$(VERSION).tar.gz $(BUILDNAME) +endif + +.PHONY: default +default: build + +build: clean test + go build -ldflags "-X main.VersionString=$(VERSION)" -o $(NAME)$(ext) ./cmd + +release: check-env-release + mkdir -p $(BUILDDIR) + cp LICENSE $(BUILDDIR)/ + cp README.md $(BUILDDIR)/ + CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) go build -ldflags "-X main.VersionString=$(VERSION)" -o $(BUILDDIR)/$(NAME)$(ext) + cd $(BASE_BUILDDIR) ; $(archiveCmd) + +test: + go test -race -v -bench=. ./... + +clean: + go clean + rm -rf $(BASE_BUILDDIR) + +check-env-release: + @ if [ "$(GOOS)" = "" ]; then \ + echo "Environment variable GOOS not set"; \ + exit 1; \ + fi + @ if [ "$(GOARCH)" = "" ]; then \ + echo "Environment variable GOOS not set"; \ + exit 1; \ + fi \ No newline at end of file diff --git a/README.md b/README.md index 1273ad1..2b2c8e8 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,144 @@ -[![Build Status](https://travis-ci.org/ameshkov/dnscrypt.svg?branch=master)](https://travis-ci.org/ameshkov/dnscrypt) -[![Code Coverage](https://img.shields.io/codecov/c/github/ameshkov/dnscrypt/master.svg)](https://codecov.io/github/ameshkov/dnscrypt?branch=master) -[![Go Report Card](https://goreportcard.com/badge/github.com/ameshkov/dnscrypt)](https://goreportcard.com/report/ameshkov/dnscrypt) -[![Go Doc](https://godoc.org/github.com/ameshkov/dnscrypt?status.svg)](https://godoc.org/github.com/ameshkov/dnscrypt) +# DNSCrypt Go -# DNSCrypt Client +Golang-implementation of the [DNSCrypt v2 protocol](https://dnscrypt.info/protocol). -This is a very simple [DNSCrypt](https://dnscrypt.info/) client library written in Go. +This repo includes everything you need to work with DNSCrypt. You can run your own resolver, make DNS lookups to other DNSCrypt resolvers, and you can use it as a library in your own projects. -The idea is to let others use DNSCrypt resolvers in the same manner as we can use regular and DoT resolvers with [miekg's DNS library](https://github.com/miekg/dns). -Unfortunately, I have not found an easy way to use [dnscrypt-proxy](https://github.com/jedisct1/dnscrypt-proxy) as a dependency so here's why this library was created. +* [Command-line tool](#commandline) + * [Running a server](#runningserver) + * [Making lookups](#lookup) +* [Programming interface](#api) + * [Client](#client) + * [Server](#server) -### Usage +## Command-line tool + +`dnscrypt` is a helper tool that can work as a DNSCrypt client or server. + +Please note, that even though this tool can work as a server, it's purpose is merely testing. Use [dnsproxy](https://github.com/AdguardTeam/dnsproxy) or [AdGuard Home](https://github.com/AdguardTeam/AdGuardHome) for real-life purposes. + +### Running a server + +Generate a configuration file for running a DNSCrypt server: + +```shell script +./dnscrypt generate --provider-name=2.dnscrypt-cert.example.org --out=config.yaml +``` + +It will generate a configuration file that looks like this: + +```yaml +provider_name: 2.dnscrypt-cert.example.org +public_key: F11DDBCC4817E543845FDDD4CB881849B64226F3DE397625669D87B919BC4FB0 +private_key: 5752095FFA56D963569951AFE70FE1690F378D13D8AD6F8054DFAA100907F8B6F11DDBCC4817E543845FDDD4CB881849B64226F3DE397625669D87B919BC4FB0 +resolver_secret: 9E46E79FEB3AB3D45F4EB3EA957DEAF5D9639A0179F1850AFABA7E58F87C74C4 +resolver_public: 9327C5E64783E19C339BD6B680A56DB85521CC6E4E0CA5DF5274E2D3CE026C6B +es_version: 1 +certificate_ttl: 0s +``` + +* `provider_name` - DNSCrypt resolver name. +* `public_key`, `private_key` - keypair that is used by the DNSCrypt resolver to sign the certificate. +* `resolver_secret`, `resolver_public` - keypair that is used by the DNSCrypt resolver to encrypt and decrypt messages. +* `es_version` - crypto to use. Can be `1` (XSalsa20Poly1305) or `2` (XChacha20Poly1305). +* `certificate_ttl` - certificate time-to-live. By default it's set to `0` and in this case 1-year cert is generated. The certificate is generated on `dnscrypt` start-up and it will only be valid for the specified amount of time. You should periodically restart `dnscrypt` to rotate the cert. + +This configuration file can be used to run a DNSCrypt forwarding server: + +```shell script +./dnscrypt server --config=config.yaml --forward=94.140.14.140:53 +``` + +Now you can go to https://dnscrypt.info/stamps and use `provider_name` and `public_key` from this configuration to generate a DNS stamp. Here's how it looks like for a server running on `127.0.0.1:443`: ``` - // AdGuard DNS stamp - stampStr := "sdns://AQIAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20" - - // Initializing the DNSCrypt client - c := dnscrypt.Client{Proto: "udp", Timeout: 10 * time.Second} - - // Fetching and validating the server certificate - serverInfo, rtt, err := client.Dial(stampStr) - - // Create a DNS request - req := dns.Msg{} - req.Id = dns.Id() - req.RecursionDesired = true - req.Question = []dns.Question{ - {Name: "google-public-dns-a.google.com.", Qtype: dns.TypeA, Qclass: dns.ClassINET}, - } - - // Get the DNS response - reply, rtt, err := c.Exchange(&req, serverInfo) -``` \ No newline at end of file +sdns://AQcAAAAAAAAADTEyNy4wLjAuMTo0NDMg8R3bzEgX5UOEX93Uy4gYSbZCJvPeOXYlZp2HuRm8T7AbMi5kbnNjcnlwdC1jZXJ0LmV4YW1wbGUub3Jn +``` + +### Making lookups + +You can use that stamp to send a DNSCrypt request to your server: + +``` +./dnscrypt lookup-stamp \ + --stamp=sdns://AQcAAAAAAAAADTEyNy4wLjAuMTo0NDMg8R3bzEgX5UOEX93Uy4gYSbZCJvPeOXYlZp2HuRm8T7AbMi5kbnNjcnlwdC1jZXJ0LmV4YW1wbGUub3Jn \ + --domain=example.org \ + --type=a +``` + +You can also send a DNSCrypt request using a command that does not require stamps: + +``` +./dnscrypt lookup \ + --provider-name=2.dnscrypt-cert.opendns.com \ + --public-key=b7351140206f225d3e2bd822d7fd691ea1c33cc8d6668d0cbe04bfabca43fb79 \ + --addr=208.67.220.220 \ + --domain=example.org \ + --type=a +``` + +## Programming interface + +### Client + +```go +// AdGuard DNS stamp +stampStr := "sdns://AQIAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20" + +// Initializing the DNSCrypt client +c := dnscrypt.Client{Net: "udp", Timeout: 10 * time.Second} + +// Fetching and validating the server certificate +resolverInfo, err := client.Dial(stampStr) +if err != nil { + return err +} + +// Create a DNS request +req := dns.Msg{} +req.Id = dns.Id() +req.RecursionDesired = true +req.Question = []dns.Question{ + {Name: "google-public-dns-a.google.com.", Qtype: dns.TypeA, Qclass: dns.ClassINET}, +} + +// Get the DNS response +reply, err := c.Exchange(&req, resolverInfo) +``` + +## Server + +```go +// Prepare the test DNSCrypt server config +rc, err := dnscrypt.GenerateResolverConfig("example.org", nil) +if err != nil { + return err +} + +cert, err := rc.CreateCert() +if err != nil { + return err +} + +s := &dnscrypt.Server{ + ProviderName: rc.ProviderName, + ResolverCert: cert, + Handler: dnscrypt.DefaultHandler, +} + +// Prepare TCP listener +tcpConn, err := net.ListenTCP("tcp", &net.TCPAddr{IP: net.IPv4zero, Port: 443}) +if err != nil { + return err +} + +// Prepare UDP listener +udpConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 443}) +if err != nil { + return err +} + +// Start the server +go s.ServeUDP(udpConn) +go s.ServeTCP(tcpConn) +``` diff --git a/build/dnscrypt-windows-386-dev.zip b/build/dnscrypt-windows-386-dev.zip new file mode 100644 index 0000000000000000000000000000000000000000..ad7d185599cd667596e7f29dd65e2fddb6da23ba GIT binary patch literal 101369 zcmaI6Q;aT5w66QLZQHhO+qT_3+qP}ncF(qL+n8VNAM|D&J+fB}$zvi~S89Hc6LfdPPSPyhhse^F$mM8xD(#QqQL?LW}}#Q%cX zsp`dNbE5j5YUn;^&UV*VrSe8DX_6sgEne|*8D-OOVBas&GL*1{+B7?Yx@{%j-&Ag}$fX6*12DUq zDf|m?Py3*X=}(L_^;>Y=nD9g?SbfCt54CQWbsty=Tg?m<@_QiIb9*SgUrI&Vu#AmA zlkoiThNpL{le2$7s*@FlzkWL05pX-FZ%=nF8u*HNPm0W3!n&&I;T^HP*mD=j1uK+t za~x|j$MicA?uO)l0AVq&Z~v9cjY|`Myv|$C#WqiW>G(43xz_94@DRg!!PU1v^`*F> zT_mVg2+rfgP=JKUZ36pM69AVg+61z65%O_q0VxZXyRK0eTZ=r(&fLG3y1Qr`{Us zoy}54&7M{DpM2VkNSX9i;#U;dijER$bP8vGPu;oHV2B6xzgV;%jZnp|oK<5A{$`|H zHNaWvmHof=)G71|usXHks-!IgHM8Qz>4gOQ_E#`3)xXm$g=#ZB4lZC`^6(MXL_l>Z z9O&|?yP@&O`UKYRCuVeFKy_IIeHcgk?3hcA(7Y;NTnxb>0wWLA1LdnnHB7+yvs8LN z9zHpIVQU|j1Z8&*Zx(6aF#lHp|D&VI%0dJ*KOn%YKNtY>e<{G!-r2;-^N$O?nTOf` zOHneNVgEAH z;Tf4=Bc>)+^VY81lbeXY6Bm|EnHX|VqVM{Z49Ku!r4GQ=P=c1VSN8qi}VGkuYw>8$P$M||HuWjBz; z283e4J{2D3SEPhUF(DE&{r8DjiGOJEuQ3h*)cS~5@Q?RD6{a~1BgBP3RGWWE5A+&J zOvNc7J7x~Lf`#w5C4D+kBSgtS-n2m!hm5nt?rfo;Hn9>Mq_RSIy_7HK2d!-p;wj7?1pSUCabM4RiWcNZAL#RGrbCl)Ok z>5W*WmLnS5)uW(P%HYc~P$Euy=P+^-WBAAAz&4dvMZqgL%&E;#lghRJ+ zQqo*ZrWY%ytCh)sp3F39_6e?*LQ2~cTP7mEm0db+Xun{fJdq|hAa>}{q>Pl0h?R)# z9R;Ic_}Wih+OIVqssruOij;A&J9Acv7Q{#_+tpADIU5)2EKLb1r58FB%SCkztw&(I z-YQ6iqmIIs@H|2u0)Y9Lofcy7@u_W^UBrZ^YTcrTS}cJGqACGmFheqfiKPdbL*vr+ z2vWMT*f7kj^ZYm3McFt|Fv<(EQGv76*_HcAt1GYfgUD3+xGL7#%i>!tyyBmFRzq6h zsx=kc&Wg;ac$t(zi-q81KkG*S7U@FCFb^}_Mx1&%SW8|zk;^un_Az)|Pt zF|_5!L&HXb^odb_Kt9q&;6VDL#Vm8uj85A)>7U4|uCo_Hg~MhIY2(IbEwg9vUz9;Qr`ZW^P;8A^NWcIp4JqoKv*&MG343=D#Z4X zuL=?vTMKN+f|ciImJ8}~ynef1o{v5=jTQ4(2Ked5PZ$bRIuq%=amZhD7n(~PRy6TOl^aOGJwlVz)l({IVMOA{7s3{;~FsAPE zjN1q!i!)JA5+>jVsM?revG#r?1kzyRJZ+oNgTu#9Jp+vJF97QH+;hHNJgXa!$`u@> zZ0E_IG$5Pg(upik!45TKaL^i|Gy$2X<~PlF)Frq3dwjfz++Izx*ug`_&8cuk*hkn3 zAdjO&=0UiKwYF{r>R{f84-vL-W2VLAyNQORUPJ;|mEXotH7cT9N4Xj~b_HjL8XW4v z@JN0b^6wZ)2LkkzFcy=kSKAr(CrutH`AplqVpRRF z;Snt%FHv*pAHq{?0g9j~pcW8OlDZQZxv_$rGLG~6f_Nln!lEfIN?$zMP_0Mgv2!uv zB$^}93|B+ApyJZqCR&75<=FT*aaP{niDsDF(1uqZhPKx4h|>Xred}~s_nI+C&#_!VOe?merwx1tC zIRoN1981j7)Iu-kw_ao-ELs%hB3Nm~J%UkE%|;1<=xGi)Qp9-%(*7nxTA_J@B-UJH z^}3I=%lmkSLGjvofaBTD5rR<+ZQRr&%!h1rcCl;w@Q2jie%;t2Ttq}geyq66A4qO2 zke0V{j~^dO_Lo%TeBAQe04_K$o-|buRPGYV`#2CUG0fh>y`sS(t4Ppe@}co>GODP^4Hb^NPnfKV_sKOqwS46&Whsd_s2 z2f+Go#!Eq38b{RmrFqDkrtDF4IP9YkZ)n#M-$*SZb)d9DeO)fBT+Kg~DIoy{?5i8Z zzsL>d%cjFeJ+(GKPm%E5c8N%GKK{JOx7RwhdmSseu#P-!UZYhV43v9s2(Ogphm;YjxMpDs_^b{UfITGHii!iRtlzt>HfN0|g5Bg_RP9n83ADPYQ&q;c@i7`(<=$&xvQ z=8h5)b_fp=%B04(_Bc(5)+vx~LL{fi3}{J2SOzJuIAtJ=FsO=d5;q^pLxm8wUdZxi zu^RaXZzzPkp|P|xp3a71GJ&2u73T2xAZ7Fisi{K}^f2#aB0F5a$-ot8zV@H%k`Fd5 z7uX9!K0Q3Fn0^7;%THKMUgJ{9Xg;$>)Q$4X!Jrwy1#x0PR_i$J+heRgW?Gt1bs(G3 z%}~2AscOx*ucNB5Un~6G0AgO^rb>yWDQRAO!3@%hF6dI81YfaXQ1B$ZxEE2rxH0#c z{4k1=e^G!%5&EMVob0eswA$T~Cm8@{6}Y)lClgQkpwBUZ+nL_yjTJIIE|_TJ8#df`R>KED3UY0mr+A+2#H{7(jYPBxTc3;Sr#y5k`~Vv`a=Kmk|4xI zL}&>DGFJtVco`h%P?4J#BR&nc5~|>LEXLokN*)gK@Zw=RoR>f9&q|+#6-a!_kS+S# zlH$B$re&w<6`XSzToiA-<)!uhT$!GbJ}o`Fuvt2iv~<&FIYSiMdhW6+7sOm-YQxMm175FIIATv!`P^qFbn&QwE zq5UV}Db&E=baFv?mjqce)Z zwwMxC!zuA2@WWgz^yo;4YQ-#jD04f~rjq;`E|A|K(qq(l{Fu>0Oe;~rd!x`jNt4Fb znh}*w?kPhRFX<%=#Eq9$BvTHO>F}&i;=%-e^nq)|pc&Gp6Ob&g6iO?`>obK}jD|*(RnuxbVLNe;D;i6^XHldShU@w*LUTZXnKyd-5qf8Nl65 z-*1en7qyTH^I|8ZnuE|IE`*DA+s2!04wVu$*@B_>4!@!sT{EJd(-#ZUMt2qa)a&bu z49d)Z_beh^;F2_kJTsHS(SzSN=_1upc))qICc1iI1-$FYO8AXgl&Q`TGOGg(g9nu= zsckk|Ro*2G#^5YD#Hy(Eu*Ib(rk$k+d67$IRiKtv@#rl8#By$jOxPC7vll>EpfLYZ zCML}SDafV4Cb6Jnh^ ziJ?Ry5;3z$NSaPZ7jDDmvo>oGCLZ0E1JW39?Hcj`1(R88us@EfsY`!N`WLvCo({eC z7w}+Vl&A{Eyw#KgQV2xvk(J9sjka+{OfiiKcc4q=!*Z4zyD)2TJ#(MVc*mRcXvBb3 zLk$R7IDC1qzSaX39lM$eOiI8xk#h0+_a$Eq3)M-b3uwjC()SOK_|_#MX<2X!koCaN zyUt)ql1&OUPa0X$(Nx$u8EApp^stYn2nj}b?co?C5{(naFbPLwwhB8MK?if$4mk!I zcEZ%OYZCVl4>LWxMTr|-HOW4YLsfa3${5Ozt|3Q~(c>|&?g|(KWG1yp87O6OyP24( z!E1Bt?naLhGmbhFSP4T}eOkO*u<1v*Xk}!jFxL-v3RINKi5$eMm@H(Gse%Nmd|pP3 zHjN>sm4_VATiirLJS1^qAQ>ZvetrVf?;t?D6vHb84#No)X^26TS%-szXt7Vpx=HRk zjU@-=SO}Csmr%=Uj!9@RsxDkzW+J66th^5-6uyg>6G7x*nJM#7^T?xRMn;Cz4ywSF zw4AL$-s^dwoT+D!tvr!nfor@BG2CksDYymFK>KR|6(oz=(LiE%!5TF#c#W-wsvxkH z?Ii357dHs^O7m_-J(|w|bIcJR(KgiIrk-yzmxtCcv8JEWc={2QB}BbVg_8=Uyg-)2 zDHml{>^RHWD26o=fdKU|JWSctNH-N$`3@Pj1}j68b}3pN!x@S&HETc z%Gae-O2Ud{@Sy*!s(+dU1gX-d7zjJc8w%ho8S;#F_Xu&+qxirKBfVI>3l*=SccR7$|P1yx2&7Xi)O>CmvB=l4XN)$$N+C>zCCoiubrDu>A(=hw4&FhepY=Y2-InpRZ@B81K6@B#RGvg#*)Y|tpu?qQ9jh-2io#>Tv(g63a*?~k$1 zTs2m)GT4)v))~e0p6=w4>PYUwkgWx0`d@nM@ahvU&YAJpb>ERIVA)Bfh7Oykf!Gl_ zD7UglkYP?rR8>8AsCg`nZ&0n5SQFsrq3p&DOJCNT5-WEnqUK)pbts{ z7m`O_EXj&JpxZhcizr2F*(YvyNvz6EuG7{zNZW1@q_S~$gd$9^yu_#^TSUr}6Qp~7 zR7F;u97i6p$(bItH`yy+oS5~*Xex6+t!w(C>_#eod05owzR-BvRxxTlS4qQEx4te{ zc}Lg}|0v%9lDt*kTkn9zmXx0WY4&z2A)LebsR6KOlF`aU3`O50P64h2~RRO7z%I3}3!@7ifa za}QbE*7n9!9dfo2Mg^qVT5o0?&BH5620a+#9&p)OSC~*0Da2pnb&IH;S|*<$S8Fv; zi&lMR4yJ*V$Jh*;gUlSk&ADdyOT4v4y$iVW1v|9ON?lr))mc9PZyKHH4|N;DPYKOs zr`XZq*$4gs`A}-UNbQ@ngI>BUJ=!kWxb~1(e_@jKI)BSN3%csR3Jtc~pS;mJhvKX5 zwQ0lFv+a7H$UJN)$Ih*%eo=$;#FSfqiT;H}oF?2dumUHx*uq#joIsKGQc|3$CX*yy zYQA1dle$RgNvmJ3Yu(zY&^VgI^GVJ!B}8P+NT1?HCQp&f!IIREdwhUgxo^U8ZzB*CZZ+6wR>BVdAmKsla@1{rcVEUb*Uvwe! z057_*d6X|T;B(MdS|E8Vke{nTx7deWz&>rrd#U?dvOXGtw@8Ow;64gSdtNQbShvep z%e4}acA`q^)g}_z8_$-Zy~-LU+zNg8wGs>WfbUc+BTGENdgTJl0=%lTofQ5Yf$7M7uQ^e)S=d+e(w#&pLnb>S}SSm)b?oeza?g!S4_rC*lz=`3P~&K<$e0gV;E_#a&mmsd}*%@r|sto3<Sdy8Ivz>%%v#{*w4Kvm)ooJL01E9Sf=_|T0#jRQ~chu@8KJkgxIIp{etiFi=h?!IH z)+Z8EV?TDlQBbCz7}FmX6lWc#26mK3NhTd=GH)nh0<@v8J%edoo2Q~$af6jBxiEr4 z)q9zk&FYwVY^)b!(Xs)&GdmZGP$Bm{2i8XWTlyDODs9d{Jm)6u9s`q^5vlbnhp0g3< zlPsj{ztakL!MpU&YIu+46C$PHt4?;6ZX)M<(i?T66PPFBF5rI%fz!AY7p}oulhrSs zm*2T#MKeIGupu8ib^>(HKw2z|Wf?*`mRgljw#1XaVai5OSmUH)KiB)x^tpqOFFLJ< zZAu|kG{ftJIaDY=c?RhN!6V->xK9I-_Q+9u1W_3EIT*=!L&^-pLitq8>|JZ7=d~p* z(eLM8kS+LsdTOSl%d;NqORFdKjhVc?ls>hv+#D#q^FrEe960vDX&;MRk7;6l`qI>2 zYb5Fny5fX!Ce>4Sbeiy)JbLRH;RRJ8i^!*xcv2`0Qm^YvKPOpjhkD31;mkDBSs+J` zev)JqN8rI4z(MFm(7LDq-}DP>G43Dw*NzGW#h4B_9_!GfTk@K3ziC1OyY|NVAZ_E- z9QdF85fD@Slp~>jHMS%N-3%!^c9Tk#9EHmH8S0qD2r-Ik7ecP3^r8qc6&zid0Rrl+ zeIe==lOQBFELNIvsuuNBwcMBq%DFvdDHkp`G}S~anO!RtLo4TlD}_GoMF za#qz05;jwE&$wQ&nSbi8)tY~`M%C-+5Kh$@MGB+4 z##LK!*+H2!FJR-M~KCOZ{S_ymlgj&*s7yMwK7D z1HWQZ`eT{ji~9h;d)AwD2o8V`1B>}z31%;_&sz1{>iV!25^jtMtUo01>RK9nTA?hQ zTkV|>=$PqC)bj$YoyrOPG^195QM?5Sg_gu2Cn&Mg&jx= zJQ13q+;ezRh0RHd;4VbG$#oL?*Ix{XM?FBE&l1209ZIF!+nY{M?Gk~ws1#1 zdTzI(`nQn0hwOpjM$zk^qfGvoVA!uDcPgO2F#^Rr;x`0RZ*!AMh%n;gWX1UX_N6sQ z`b<&7`h327f@?-oF1%F#D{cy(u)Qm3rWcs{Qa6_E3>%_}@ z>q3M?vuDLL3J_F)FbaI30U0S$O!Si#)UtjOiF?prU9L|+aF4`a4Fc*+w{SR+4Y>#km`IY8B zyy%wx00Fcy**%U;wYXL_UC!B&!s~riR2#iM@P4u*7ZM#IwTeHp;3>rbr(;eHoC000 zn=7vV-m-SH0{dzAtjkfNvcnVxwg69Fz#i@?G|)-*5jD9b)IJRKn=vlF9~uR=?h4?` zF{jRhPRfg2Q|VK@UScegMd42-(7hPs8E1i2X{e{8i^NpN3b#Eh9|1{EMj!eiVDAI2BXlg3RnRUMlGi@&1pP;wWd-{sw0i90Em5&=_gW3 zOs(YyE?HGQ&F(}}7I#}CKvnx(7oB72YX$QJY~53>GqJh>Tg!1;o4vkcW2FKTKWoZQs+{JF?OM1qjs*sYq)`k_-q2fO|#*(;ZSIx_yy{eyUU)JMTy!pwb z#$B_1eeT7po!^Kay~fg;*Zyg*XcDLV{8`?gsHQ$RZ%l_?#G6Kf!3)K}q! zgz*BSyKeEvXsV@-q#(&a(2+y!oVKjKnp$&Q5L~EZfnz=`?8AENwS|RTWcY`=#R67Q z0B1l`*dea64s6X8bofh^Or|qRTO>`{H&Lsqd=wwo)z7se@jtD3!3NLPi(djtHzk@j z)g2c!^^QsDw*eD1a*1CENUjC4M!!mFZmYFSqXk)`E>h}en)G^`#NV#+_BJKoI9LeZs@ajRZJr)zl@`%omMeW%gzzM;Bq~3@~kw8XEmJy zRmiFQjHAPRyrcWOOyc3@2X8W(_OF;7rrx8N+PficIg-))e&OPb6-%Ek&$v^k(hXCe z#?O)4KxAjZ`-6BS2yq`;VGYf*1UO9rSq-?I0qEoI$Lokw6NctEO}Zi^tQ>?9jl7I6 z#}~b)W6ZYE|GlYBd|%kG%CC1C>oqv`Gv-E#Rl|$ji{lsgGu?283E@T3O?Wh@nyhAb z&hkx;4acXRR&(7$aINo)R-dzAQVnsI-ObNSCC2~N@dEov;egi9>(>f8Vquz}#G*KG z0+Zt4!4VJyVw%rR&&9u)l)el|$NVjQK#o~ykOOy7>W_p|m;}TDHb{;L*EBgnoP9rbYs+qvtGoXuTfks@ttVWFKcwyn^2xV27I&mY`aiD1Od#LFA_HOX7@R)?^X6An8}P` zqaH@*Gb^WSjE0ZB!|GlV3Nq-XIHm?58=qBv^c@`^ofFg7+KjXP@+^LXb#)zjUEa(U zIvyu$keFP_Kp+syDT0s-GgtqT{@3seipts<(~e(D!I;S!MeUdsAAyT81lUkljFv zIEw?}6oQj3M(mcsptj||#GtssYo_F-i!@WOtzP_|};PrTu zollZqobv9tb{=NFa*`=kzR1mvlax{IWk$Hzt1nyHS@ zoJUCR+NemxyXRLc_DR1-H@olTMV3W&^m-OS)nA*b0UwB-U6&@H3~D$dlfr}Rdnd^! zX62dkBNWF^8T5zr_+M%$L6}SUdBg`_HvGn%*X9!~#j14088HcEk2UjJDD#>08O7}A z0l(#SY)6sW)#Q&_Ad8n|OY|eX>OCV?<5Yi?0C`gkeSUn+uso;W8L#dtAJ(mYUJulm zhc2U{oN#&pW>aGW(_2pE&;CS33zw>Nu3F=0WZ>d=(MJ?%$g90jlzbIo8$&nUFlqs{ z!Kv=)he!eHLVgD4)#a)9B$0L~9}XI#S?{(9re}7tv#^5l%erFZj%^1$kI`b*2-R(GZ>H~-zed{vRQ-5N8U{%&{MHLWsNwRqg~;H24W9I ztJ$ny{I2|}{RA|53Ke4w%{hTeZ;>xs#d-gH)IK9^I5lSpk4Ub#SU>z|aoDXT0I{7bQI6*jP_z?WyDKAXlu4WIERK=*})V z>SOZYDDVtnIxcKw6f==*&HxgyFIngewSIpYARE-X%adW`&j~f{+|{R6u8v_pB2xp< zU>uytlPz4>&fIm6Y4cVmoWp)68m6D=|BjIZPLMpO^q3(VicOlaL?{n^G$>y?^4Vq-nC<$*K9SUgq@3X9TVcwSlSkOROg%1X`YNLBt5?lY0k>#ESPM83 zi^e|x%G3G|eYsKs=s9V&{7X}(mi|Ej=V zsq%JG=hu}*T`xcXB^_=pN%J7l_FnGu`}eNE_lhZ z9DbKKzqxHo@SMD4iy8Y|%H{d$G5*KUwutby>vUV~1C|Vi9FC~N0s1-$VSY@^#>Q-! z{fuGMTOa8F76Wy`m-l*NF4ns$?f!-#O>5MXtm?qYtg?yB`7#1ed`IJqYUf=d*iEZ! zyq9O{)!P4=tFtOd%Xu0I&U=m3QexGE$m_wf)IuH4d5-mD(!}zUpD)%-9kYIc*VCoP zEN|V+-RKdQ<3B$bZbaZa+sMVfr!pcVe^xG_OqfwOpfgMFcmfn9+W(8=@z#G%Pt_pk zhObOhqmaW0#2JD#y9<9s;)L!ZD<=?vOYK7q4M=8Ek3qy(3$2T zP*5wOf^8lLUhKoSn2F@wiYC3O4P=yy&ZMTM9bDBe$#IAUC#Qe>m3=b2T#?QOeN4Ue znYHXPa!W>RG<~~b5%d07fbz>aGpwE<^lQwvQ~!%XEOI?!omMPzyX4sI{fW5pWX{QUu4fDr zlU(!On7P(Se&mOm>v~KA^Uvm5s%BgI{qh&-n7ps^bo3G?>-DFTCwi}Dtt$dQ%=akh+gc%nLzQdiDJWD+;TxyhshCa@ozLNk4cWY-V4nAhDNb(Y49( zDG#Okgzwo}%qyWe#qL5)=DnXn)}e+io|gKzsP_)17U%CJ=HXXNZY0JJ_HP|o-`c*x zlNXz&Y{2(|?65s|()g77Se?yUD;zmPAqi_pY$D$=2zKth@JlFElx%PMWC-1|XALw# z)B=B;NQ&9_e6KM}Ckk@G7R5LU2rxq{Ml(v`r$hc-FHZH}+b#u=OiBpf`A--)QKEvz zvZb=YngJp=bh?ASt@E09a5a80la}1z_!0j`wEBf^O#@@f#Yw(E*-NWlTaAmBt+1cj z-6tF7$6fbgMPXRIuw65OkLNP(YY|rm+{$aL?v-IY@fmxUSr`;d2Y~qboAT(ENZ5U* zYWRADPYg#%OAUH_KO>aYy;2UpXNAe{Q563NN32F79nYn@)%+!%Shb+UW=+pXOYLlg z&QGn$U1MSvp6jiPpuugu#mid#B4agq?P35?ZO6!yUwEvZjZYc8HU`eMW>uy+e*}Le zi4g1a6t}J~N^|$I4zzKjo~FbAk!1=)Y+J@`ZFogK_Z{y_tGaH*f9=q9X}UU9v0Q_r z{7D*Hm@YoU1$K%1lABBxdb~w`o3J?8yjv>H<})1pk45Wt{-6-bMM}b!&67(HVK{5Eel3Ti!wSh1%k!xBd?f+( zU7EtB(oXM3YI8^M^O?BndvTIa6^2}}>_5e5_v5S8NoNW&OA>`+YBJWTE^D{3<*9#! zTTrAl^?4yBEOqHZS*>u%ipO}#h^!RcHW#B(RlL;f3euppc1f4-UZuvH9FT=wIl(gO_Qs{! zZ^@aoqdWP?e;A>d{3UE$XecPCd;4}(JO}=XHeb~{)au9uR{*{8s6rbDp}4P3aOUp1 zbDu2tuX%o@t z<=!-YFq3EiZZH_epDd)B&_CFdHl`e;T?;d|eBOv2qB)=tt;Qgp8|JOCzSy=+%^b~R z&8ac3?oD07ZW_QKS>agAV9UzA3Eb5KnFGD>P-}<;gf(CaG%vb=f20VnQ}@sNi(c(T zF^o89wy%Up>u2qSTJ(7$3xBk@(7$E?Xa8JFnaH4uuekDleuvy6WH9c@lzB#$@ zO?C%b6Yrv6!NCT+gvEPy9)yBF1v-eiz=)=|dQ>lnm*|AwEO}NJP+~%AABnd(G}-e2 zDT_U$e9x@BA&R+SXeeCxu#Yn_Lzt$VxmiV9iT{SG*>CWQPX?^__uF92lyUg8Yc zOF)qS_6l_&^<0QV-|(I25lnfyzC|30w?ORZ`eMWuS?i9ZSx23b)AXr|)3w>A7pkbu zJJb4Crm3`gO3b zzLe;HE9+B^dPs9No8-cB3;XFs>=dR_jkfY^-i9UEHM%WsS0i>>4;AnogVlUtL z)En*nLk=4Vq$*Da9M0O51enaJ0>N%sW5y!g^*IQhoMPwcg>)9s_qcj=oOOh8pda|U zPBTm2dwn_oS8G6h)k{wp0s6u6xX03ZG&Vwv1#8+&?229i2%F%Dui6@j%hm)qFM0Xi zUV9&CN=W_aQ7Rbgz|)IIcgUC~4ML0r%F1ObN6}(*oao80a%ug=`X|mFH&(@gnRr@{ z_3$3H(0ljZWJB|=rD*CcJFy>e1r%rqoEt9%dvmT2)X@$^p4&dpYo>tRlbYP`3uR9n zUC$J(*Rlfc=yznc3GoLGyfNI74tm5AVVWfz0Tof(Ypx0}+-C|QKg2(?ZtQcjGr2*O zmV!2e!h!INdn|JYsI3WG8*a^6pG_CQQ~q91k`?nV*GtzZogBk2dVa|I3M)3x9D8DE zWqO2(M^7XzW}gJ^kQ#~pFP&DbwO2UlJH^Pi28veB2ZyUV8_KEjg{~Ctkk`d_2%f=8 zZNl|u-%4X!u}=11cXt2lQDb+PvEf*!2)-enro}1R+b)C1o2TBNEw0{LX&($wp`mnq zT3^`#&5*5kn`?CwsoQt67f2nJ45J7NDUf9`vYXKI-5smqDY)z7!f)9iUcJUZ5?z!EGmZu)Wa z34+4dP5O*?qs}8AmDNxOy3@EPoNHaHk0&?%-L^$mAHUw5{=ly7fRL0$h{De#)(; z3$2u&21sjjFL0i~Df5dDqM(5a(IXwF$G)#b>9CO2SEGy8n8tI;0XmBC6)m%Z`Ybs+ zf5kngwG30_;W$yLTJv$}41a-Jo@*)^sC1&^OL$s7UEk^b`C|Q<{^)J(aL2B+INr3H z6K$E1Ka|^Ti*fq#XT?AJmt+TR1(= z7f*{wKK!fNUw=!jIgO^Yu3Bx{n{|2J0PG|llp4?j!4N{1*)uLQ1ZCXizdKQe2{Gm$ zH2DpakmX(y`Q~2YP`L+qGuf^4!xdy^JzvPG@8$Q*coFjn5gN7mY@dIw(kq;{JUU1b zb*oX#JiI%dN8B+keL9+jK7Xx4qh{3}TyHYmD;*4%uZ*Sp?)LYXCL9Qw?r zk>TChpa6=L*>)#d$hS(>eg~1%(=AFeMH~0`27PZd?b-lyPl5XtPl^1$(BMNm%!rZt z$*j*tWG&;L*!~Y#3iAc6p{V<@VGP-Y*?qS5o|CQV5)8&xPa)<6UU9}&v;B~C4x5Ot zDc8ILv3(-V$i9+C8~Dh-Dc4?Harb$kA3Il5tO)1)ps6lMd6MObp|`5Hj+$D0c(|Ac z7Kj=7Us0+gunBwk*qlq3rI!Lk4C-g>guFuy@zWF=ECEdE6VS)E-J(pIf!{O}QFA0$ zffJ05t~#;LS3Hb%n71R`RYMou(C=*ABW>yuyah)7COn$(IeybKD_h>IwlfH9+BKGi zb#P|>H%2@0>X+n6&iEBz;_Q)DGFThwL^lO;^1$O66E&ru`5KnT2*P#CNk?E^SwgYh zG@77&X4f>=U0JtK#0x6`QC*!U)y0dnUh|bvr9xP*l*vcrCwVoT+Jk7#~7$$STQvk)sCF zc2&fr$G&`^Y-C*RDzk_=(X<@Sr2lzRG~oHMD~;%64@52(&TOnJHB+$haq_L(c$N!p znY+uVUe$_wxy(JSuwGm9BbLp$mwwgyr+xt-@}H39Lmxu`4CD`@g{iAtljut#B(eCpxJn(8M7it5G?a3ID@SwU<7k%-- zbP7wyJx(NHCnLTQf8YwA0bO<^&@LW>@qB|^NW8klw(SERb;M}mIBS54PK@J8zaCPj zy*H`$i@&NJYOQGl!s)9Au_TWUM`|?+9E8HWx65*%81>))@-+y|x|e6CJK!(XHJAvf zM_OU5e8fw#D+rD{^&WX7X-tUTs4nOhKsmy9N4jI!{*y4V1n|$L6z6_HKZ6@5Fvc7z zj~(Z{%Y2;UGc3)y6ejuTd>>Oik~+TOet0nERUcw2!f`zopW) z=-nJ?K(CPsK7Qm4X&V`jdNmO0>6sv-=@r%M+zA!9ShROaEVcX#28hBo>_avgcRGhp znO6{c9h&W-FPawsMoJ%JN?Ls*e|8j2o^y9EGta}Z}YMZoX_N(__eXhOi;aq*3b@i9s{(CdmXwx3pS&Fl5fb?30 z^hBe_89?2-4_6_xc?lxoGe4LrvX< z=mXxDa|GrBXLEN*SHM*V&ifef!3*yQ>rM0Fx7|#CJLdQWG%p_^k`0lO-`Hgapy2;p z!H%E@Y6Y;_=ybn3<|2hS51?+%;Odr$j|FG^?Zz9Ou z-FmQmmi(~K$>1wu_}!G-{sN&|v}(Dh;u@T%cUC zlN0C7^RkAXSqZ!r*MUZBg;MXP1X_>=XcR~FFe}a!&aU`z9vd?RHrp}3rlb>1UsB@! zrMj^HbNI{*nPJVuhs<%0&gDuilk1TllWAJ%`Z`rGdx|}aM|knd9TGm}mI(|&9$;3E zaZ8;s{cmb>Y~7;UX{Hh0bJUTcLIZupXYQv>6&2zO7XZ`h`?KYH^ z(9w<_)gk&2l!TPMu5#^#Z9lUCCB3BL=- zNBW|VMqG~%pi1g#>vFzQVE$?YJtyqql68US%&o<#YCB0&Pn@4xL|vu8d^Kz_{;0Vw z%j>#dy2bJ08Ki(Ux%D%N@5rY^Ugekbc@$`8CnDy<2Ewa*#60RT$(&D})^dwt7?y6? zZJ3Yr^m2>+JrIbI@}6rjl0jY#VSVi%qBk`;_eV}7!GzUnfn72IUI6wH7`NLY+Y)8_ zaYq;)NI~3@^q50ZpxqoBuR`ut{FsWu}sHb0oh zoYJfOCk|Pu*3$uNL&JsAvBlEaSvP{*HpSTUgrjS|oVR;G%-P(+aO&8iq*7$J^)Lm9 zhcYw7e9rS2zf|$xTmt!})oza(w~Na$KmL8cTTQxm&QEK%LkiS^`ImT;=ne;>q)&X_ z-%(8re2r;6uWbyF8=AN+1U0y#$lirG@3;2XIGL%J%cI_MIA?mEdkHS)lo(zI zs#cbJh?{>$Hn-b6>k0uwjJwphbwEIS6CUxyek^YmOuO0-u_TnAbXR!&T$s0w!v+H$ zI;aXfUp%uT3#D*RZ;#Pf{r*D9+g z$L*Yd^}#7i&HlaGcHhd#;RJOBg{c!`^Tzvly8ylSD2Dy_e0jqy#pP<%}t*h6}1_ttWOy_0I zeP^XV#~EmIC#dwh_i?^GOD7u(RM>agwODj5J+V^3b3xUp&VmgsE9UaXoiK=q-_xjy`X-0kfp*5I zRpmnwPWd-S<=WDK<0gzYR+EEUi<=w`|I~EMn*K(V&Bqg1!B$So;t#IdT2bnYF%PfPOyHknIU`T-3)Wug3d*!>L!-aXwV-JyS|0;EUuo!f-aYPAWC z;N?7KZoTJ?C7Ji2zb#qz!>bWQ55}w7hW;b2X6W4@S;TH_t%h#InZ|H4zLi?gTld+m zAZoR7`AwS1e^+IApQJ>iSx>~C3IGO`;HXYmv^lXm$$maGbIYjLebxF`G`0}jdleeN zs@-=ZnjTlbzAsV`a5`4KQNF8P4)i8S1%pFN5?Xy5C!HI=J=v_ja=h}O{(aV!req}F zX?#zf7vmHOOw^J?S-@pkatMbUg><@`1DsU^PuZRqQV~tbT<1+Uq}7HBhuoU6QXqO@ zi%~}M%NW3xlMd- zdct4xvg~IUy~UwA(DVp*eh8W0k|R)$0=yBKK2?Ar1$YZGeXEe@RFl|<%>PobPgQ{H zkm+aQzV6EqFrw%|?5 zCgPzV{gW7*62mDmPVmgcyNB-0te!O$fo5$y^ST*#&=K*#3_PU&2@M*!ZpI6jLQg)1 z9-aa705~{{AvjkavOR zHQ*%~tQ}EE27j1YoSDyN3Ve(@f5i?=7ohillsId=-h5HxYcokNtUFeVqptzT-`0|7 zopFnC-;8%=l1cmtb^b|deNUZL?RZN1s_qB8e?lU#B;X&Ryc6iGX}{}oQo3u}|9Cm6 z*$#ki2Cv3Mv0f!uFDY20fO~KW074|eo9qF{w#l0ZNcG!^fw-}qNQBMp)Pw_s1DDIy z*)wY2jPs5lMcdN;`2dc89zeHU0mEf~J6JK8Oa0iYH-4ylqU#=kee&0?SU_-;x zG<@Fiw2lojXIgtaDc8G@=2^hdl+i)--I$9SuR5Mssed^BvUQ|>`vG>vI=M9ap;b0e zT6bdyL0|c*z5?id>tK}CJZ92vJN_xaMtxo|QFp+E8@mdbi}0S|m+Js^Pw~DP-$1;{ z``GcLCq8-n%On4Y7G18{!p^y^=!N4iEnQQz&TzTm;^KFYe?tGo@muvj9RH^7;#v3T zZ=AJH_t31LbOSpw z-?*pbnUbG7t}eNB_R5lLjW-!@*6h@6C|Oy0Yw51iTgx_=zghN^Y5nZSi_X)(Sag;C z>7vWZZf}02{FQP&+EV_{kq4{?ti|X{>(%F@o#i{ri?sKdcb>9_y`TFCZQ?FAean4` zHqS0&*RXet{s?_N>S@iE*`=C4jQWv#O!MBjM@C)fJfPWDF>1t`V;(yFgR$Sv{6_Qc zgsr20a(*-B^_IJ9-#G2X(YsvlkN(=Tq3WCVkH>sG<! zbf5Xk*edJevx{}-S^qxpFV{a;q3b<=Sc9%~|8Nny&3)iv^keIjvy0Gy){m}02g?tZ z>onV%?umU<{tEYvZQJ>1vv#w#q-0ykHpG+yPvugZL8TZY~mp0-Wv&_(XAE<^{8`=86v_R(ck8782i!O`z!u6_nC^z=RGlI!@OH69-8;}il^qCd&K8|yJf-! zkH7bzapFYd;_k@erbzlrp@Y8~YPVY)F3*4P)zAq>1}It`AyFwd2pHz=Cm5#mFAQ@x z{DuCPVOqX|KQm&wrX!}(2U@Izn7fuDrfIx};cXgb_QzW0F};r2XVfv|nX=SJ&Ln&R zUohO=>?gO}2YTc9zR9o@kC6-NVyfHOTgC2vJ}UYozPGtM5(@fb&7nwl&iBBw-R&;n zTk2xHe7vU_zmGOZdispqGWzzQKe7xmF=Y$>MNCh%bwG3*?Qu*v_qjO6?m{Ft^HP|Q1SH9c zoG91=R;%c@aBdibqF=BCoEF|@^K-oDw}&*yRL2 z9T7MyIrFrV;LBmv1;ODYbPhv|D(|ZF6u=k@wp@lE-S7NU?r<8oqwr zP5j_dN+*MrAow$~R)$1s0aBp}bk*}>YJ3$qS#FQRCGl1#$2*-C4;0lc3Jy=eDSBYI zyBs#!oP;^yn{1ULCIFc^C!ZZj%R4EK%_gyVGWM`aLer?h!n8kq^KCw%B22Slkx3-$5GNABlvNcEgpF? z)cL64$sDaP`&^tPS{$5c4R9buJWf$^*af%EWp#Q~b5TyJj7=;kD@KLDcLoHzQ*`+yr`-wy%kAMM&cfLxfmdO24nJqLIV70#eqP`M0gLDXM&JYv;Iue- zMNb(1qw@*{_2t`XI-~3Wa#}=_KiEApEC%^-Q&)d{A$hbSLzBpC6x`aB)7n(5wm%$$ znbss<=ABJz$WAGtKIAXkMBWc$mlxs%4Tm-a`aEv;(qc)Lw$HTu#>f|DgPNVZkp_Dyfxf)dzmi3Fr+=SG$)ttfR^au?uWOTfI#*5K*~nG^D}3BMplLcrp| zsc({>R!f0_p^_naNy?0t$wn$SQcR6dcx4k8%%|Ffa%{4F9iX3qkrHrl9=GJMxxtVL z_$8NLbUP)NXtSye7pFbo7M%V7@3!+i7&%st+vNuJ+G!U(yq_aeIW4!uW$8-Q28}OB zq(Gh2BL!w>B#j_`z&KfCi{x|$>;W6+vD$1_zsm*k-Qx7vT^`9HxNH`c*6MOt>{hpj zgW)CmT@Hs$bXX z=U{+5sA#4-!nC7B^V~?BK0r9_=ok)=Hwm#nAwwZh8|aNUE$9O}@qoH$GkSQTM_o2m zf6hrr;}jDpo}0lbCCZ2$UJ;Yx0vE7JIxD@9%7~M2SUO8(6oWD926IUWI3y3S zDvQPAa&vaUZgE*b>Gjac4_4(ta_Qs4L16(_NLvNIPnDVA@mp*jx7*5rloEJ5$TBlT;fw!8ce2nsegFF94B#BX)-F5d1IJTR66 z9-CdT2K<7@;Sk-xiA0$Z+0|xIB9vH%N&pJ0^vq=-7Q@|=N*u<8zFNFqkb@fxMg=c# zs3`S|eYGhZk-Qg`P|u`*6pc!tfU4Corhg7z( zOn_HoNP*a7?2@;Z3QT|>9aPqm3rr$XrC+@!>$f@lcHZW4J9)dE4{)4hae+Q;v5TD5 zBd8kQ=@*@TyB!oEHz>$5mESXj_DwLMAvJLp)|FAc1yUS(B8D~o!JA0dQnHgtXZZQ6rK}$k{*{%gZ;`Jd zYsCEd@&_{-Tl;|fN=UW!f(4@2PxHrLHCw^m9C1^#--`}k^X@7fk>@;(LJTPdE zN6a_}GZfYwm4rxCoMJwDpf-O-@?*T+gVPn0i=bw_VoV69*!J?0?+}f;lpF_6GcWAx zj`G;pF~@r#Cl#g{zYq<%;?m$sr6oK;lKPZneNky380n8SU>{LEL^E9;NuiiQ16Ry^ zv}lg?_W>7^L`ddeZsrs82!gy^QCpxPl=8k~diy3aR1nO;m^mDYn@Q5z!O%s#T#^%$ zXt=o6s`Ztl#k^(Cc&Jk8VcfN%vWJq)t4TcsAQK9=W!lEmRT3KHGv~L0Y!*4U-vI(w z1o`K5**TjP+jc4q(Qk3s1E5q}{2l@CAG<-t2D4oN)6mY_okP|3b1e7Db69az`yqu% zvDFaxNJ^Wz#s59d5hKMmfS9fTX0Ht7}%1{oj5Pm)E$cW`4F~wld!r;I82W- zB<#^>WNH2#(LNZVd5>0s@Xf!emGU-hfv37C;p90658l18iB^XMy1Cuu0)52g#A~S> z4?#|)rMo-<$q8Dqg|~oF7!WwTM`5vA;16sNX?QB|p)bHn3gN|PVakJSpe8y_?$NZZ zB>eJd%JvSZMnc}zJ|~wP4|p6Du;P^349f6V?>{6$iRFcKz@%Yl(V5lFy}cEVW!V5L zpg}Uyq_)tIm34Wv$eMZq^JZS3lwbEN^fZC|gmZz|d6y5=(=cy2vpztrnvS9$>_RIq zaURa^v^i}e@8+GNoAdhv7SZKVuN}B74h~Flk7N-oe!(HQApuy}yxqg|vaRu3ciNzH z7qq06#i*KDz`9FR{qVJFHO?K7Y`}e8yw&gIIV&dxL>p(d3p~eLZM3jsVjoU^6qSh=wrD-5K{sjG7G17EmGbs@;b~|V$sQBz zc8=pYi{BlvTCCu+26z|v>^4qvI8_#}RTOLvJMRMNVUs|^v|B{H?uFO0{C1}`-_l{~ zqvrKszeu1-=Uq8gKYp&#mT|kPHFbK#NHM2kDN*a9BfjMWNr zQ*encr^f;OlJlrH)&;lQ33Jr$wm5mw4_3Fw0X|-^J6vwgotw2BuWSvaH<&UNY)uMo zBRT0-n{VdM49hcD^_>CHBBW^A5ep@6Jj;`7z@p72Nls8{-EJPL;pR9A*bmsNP=B|d zS0A>K`~Y>kJ)rrVtS$q+?&X*-h?3BV8>TPDq!Byc@QffS9+)t@hsytun z3xO4yXc~IZBa&#M6rxmasWvqO0l|UKQSeTCz{Pt!oQD@2qQ!>KoIBK*9+%sWWw6B# zb6j*;T~?{%^E_ z>1(mu>{h1(w9ZD0B-ka7$HfT_kE(p?q87H8br^G89FFj@UXpLlHe5j3a5}U&ZpVU< z3qXvi5EH90X`AOmDp;mCEwrRVO&QCQGPf)ir2r4;^5`VnV6%1w}DhO#ALCTleX z?uq!I^(FPmLQ)E>RP$J$PS8qGyvauu=A}K*qGsGWndk$xgYRl7`(8OK+LqcTsx>vM z-|y!g7B@&U-edK+9DY$0T{e%^=H#s^CEjV{fQMTpP7K(1n>_%m+$BjKtHb3Nc&l9+ zVmlwY>P{#{hrH;Xxs5E3m)Kz-g?lNMXHOb`@I=IA9vl!~%L|wxv|$Uo4XgVwN6mZgjzDg8Kt#7m$8y9Fz#{k%;Q1h>e!foD0Lt^g0(hDvD@Ech!KyWbM< zgRNiz-Wl+7R^DlG3KomUQD6x&j>b404o4thvAgYlkJByqt$vrsF4_a2rGt&@R4MPG z73Pgw^0@IyFSj5HlE?11iFT`%7kGEUC7>J>_hzhzUy^+wb;R1w5_9Z@`xg0>EuL8N z5bt2SG0CM74{x!HPPapH;*;*2V71v@V0O4YqR0unYAKI*IV79K%2@*e&dGVeKC`$3 zA_o9akS&~hcy(3SMz(S$Dm*D?jZ;5b8I;mc2PTmf_=~P2Unif;sTqA&G%^qbdt)vT zGWYh!;%2{uS7+2X2?Gdk+veP}O~Fi|mh3Lx>5yDDD?Udg*zo}_x6SFaNv=RZl3Z>} zet|Y~kPo+Wp%>b8z8PJqpE%cN`zl*<+C46w;{tBU>UT;mmuLxqYU$?!Ah&Ez&moJD z#c`ppFl{8Gz=g5W%w$7umjb^S&zd&z-FQx=DI8mn`lV$C%l3gWD5Q??_QX*SnJ<%* z6sej^&TPpUlFT!4l4Jw*LUdX^9-9sL3U9XnhjH0?n@4nbcusvVfafJaa(Qfis~F%d z4$x9WtA_)nRkDGRKK$m&WHq^_KXHLtp6%4^CwVe4gE4#nl6;>prk8w_ ze0^QLnVylw$4(@H?~h65rP8mXk{O>i#F`{I0mGXGLJJ>d2}|T4OM_Wj79{Jvp-7}p z%~)VAm?c>!mBOin7PfVnL!<~)iyd0)GzDM?S)tcdN4fi=5kajC;|*|}U1Iqe(#csW zg*C&BozmQ#FMnW0v;gvAQgV3N1m5Kicq|Tw%>h!{?#-r5_nhv zkr>}CO-VZ?mSk{vm;w$y*cIY~;h``&7>9|^bXDJ<_<7DLfe~eKiSB@82aO6wl+$Xl z+3?w3)x8sLhafmClH`{HemCc^19J>GtX2-RO`FRR82&&hjK#r}A6!fyvhW3#Hpn>$ zm<;%@FwvY*$ihVPpvkgc7m{J&q}~EisNR#MK~?fyeIgz(F|47WOJ=eXR-_p%DIlnB zx$s4XPw&v}cj)-V^8Hw;7IT(!5Mm$6w;(H5K_03CPh6;RKH_vaI17gujHj4GQh3_Y zV%BAxLPq4!7FIyG7RFRFG8!gR%@o*49U&pGBVjW%oh-9e--C-D&Svr00yaPI_i%PQ z&sl_k%?Tiv$D!VmxB5Zt_lR!1i|_VW>|o$qJT8am#NUmJjv@B3S`MoxS_(k*&h#v@^PbH zepx~71bE5LQtgd#e!EMsSsc9GA-V!CCx~#n-AYzfC63E=<6Ki0KA=xNFf8~8rSc)7 zs`+eKjfLFZJXlk9-XYioiyK@adbR|XqP;v}2fY9k-; z4C|ZhJeE&e5D*F?E#nq8B}7s5MW!z=P>Lk5Gh}}Lvu09E;QJ&wE169u4sthHZSE${ zC0IphOPk#REgldBmt=KHZb!i7hCwWNtkCWPKAHrIo(t?>$?vw=oX`h$OTg)L@Y(1( ztj;Fh34JPom27tfMBd{L@J>r05D={b7|njk;dHqiZae1?tO1xSb_XB8w^l$eTO2SA zt?n$}r-zc8&IMMwu0pA{w+e67V zh_dgEzj`FF`ljRYA@58uz8+^rk0dO~5=XyM#y9y461O1bWP~bq&@G9oK~NV`IxJMg&=nqNHozh1tSk~9%In9%OWjAtKILgb56h2ZV&jKoW}*Sf)hQG z%VOi~R`sQzAig=#DcWJu+X6g@c22Yi_>w}f933`Wu9dxx*h~nihOj7k>`u4e?YCpa zi+2N05d4omQ(G z^cas|6Kz%ps54HxO-TmgKF$ZjvGg&oz=vg=B^D4>``3cU$%&jGNH8_5oZVu#xuCE7 z9OlZBV9&UASAE~ElXr49w;yz{fZHud4hINad=-P+<&Z>cZp|YcRG*tlm_CZh7mR^L z7%|82nh)IvBA26NOF|ZrlmsFCd5n4isy`g-@9P69&`naTae&@rq{blM_x12Gusi8_ zg?OwHstUkJ6#J#Hm=itm8DVYC(kbYCSTqZK81G2=CB^2I#2kqQxz7_M==~_>UVNN8 zYdYmLdS+7PG=3q8hgN6>wU2&Bku3@@`H>q^YO}5>l1~N)IW?Wu{wc#FOHKqSql$?h zqpPg~S=H{xhr02I$n#xeh8C_!ofMsKX;gh3k;BG`F00=ya`+xerwE#j#8WVgF4 zV7eD_zfDHIiBIDSyXG-JX7XdkytrA8T<1+gS&-67L3%$_VM#MiFA4pG8)797xI~!m z&Ak|u5Ppec;mX~u$s^$;AYVP4?W4lqW`2LzpK|$YYA7KUbL^ypOif&Zl1+1)lt-fr zN2sxoX>_{a*?}TROqi-jYfFHtVOQSQ4h+VwOQJHM%h^e_mxs z*4CyHw&f;l%SqV6rxLU0CT7n`%*JOVux4#0Yx)FGswNM-Ddu1(Bz5y4v#kDvBjHAT z1Eg6VsK8SDg7o`+;I#cId^>GS6I)~oXinY0eh09mfzuq&7~v$gn!I z$PxLD6BYl;bzfyVE@q&0xl+`d>0Fk%AjKb|PNvWxBo3WIQ?M7CEPXJgbGK$%W#v_` ztWA)unwiB`AZa2bE5#wU=^;1iA+@MlMId87p&KMW$Ct&elS?0giwF2*>L|*05sm5jIJqpt-^pbVaHnSL&djw?;~?9d!XNDAn?-=rV3ymio*s4@ z!u-jSy5N~JSlZl$tvA=nX&Pvn`k@s!GyPBsJV@$%?VI^AUj0xTdC<@L>W9|gdFbS8 z{2_B#RjQ#iboM!)wNUuwkf9a{rx{v-5-AGqZRJAt;gm45r&6gMTCZoJRVa@bE_WCn zUq~yO>M@DzoNaJUHW-)6$$=K+&Mh}H%EfB(G7@~dLH11ZFq1+UMZru!_1ME84Uj&v z(?@645tjC>LY{gKK8*^UB10Pfs`qOebB?iWqbqY#*49F5e-VbORf>NSdhs>sy&`ZJ77fRqi-ZN+-_3hUgZA5HZ{Dpg3 zM53QFQ{a`%Jkj|4k`&8Onbn`J4A%77G_S<@le{V!X`cpJ00N`P-U`D|e=-OceBt^{tAy(J3wBU4kN;{X{F22uEG%mHs|kmh z5e#=(;y+M~!y$%c+xWjOe%0mp?}^=vQWVbM5674toYrue?65~pl1mSJ6*3Q{;meJj zYXfar>^NH7VQB7$MJGRe+IHeWsX;dk4pHg;PpQ-YQAIiNM9!c?|1ha#{{=PcKdCmU zUrEZ|mWNH*{jaLA|9u5jJ^+|=ookpa`-|(J{~66v`9gcRQ@TJ={fjG(znn^#cp##2 zn6hFp&G47j`Tir?ochVJ#+|n1{U%u9M~0wsK+&eFb^0l(D9Y z4}wPeWpL(+AbGXgmbve4OWE&NjtdNaJSFvN`+O_7sn{wTIB{U$P;~g=P%l&k9yV?D z7G2g8Eonlbz?K+V-G)(G}ILkY{zS;66Vby*PXtX$K|n$C}xf6=3e4@CResoYf_z9LYd|IB`LC?rg zbSbu2`3N+92qZr^^!Ykvbk$dgi{tT7ZQ*>G_Q|09m`3O0!xW1yE-M$?4SA(I2a!Vg zMLrVwYoNXXXxKH#LS(2l7>q>z8YIk7)dJ3-CeTntK$c7fUxpcyF*Y>8@E<8h0iz{f z3m=L-lgQ+-)NJ^M&wr#G1&?c$5^%^Zo0sG;hhrXsh4E3kxTFv|1YVkj&~W(aU}W;w zCck|(^okuOLusfDaX7Qh*Kk8USg%esw0dPdDV(^AW6(v^9Fjh#49_o}6BqbpZ>T&n zI;Zq4P_7=L6#eDp<9}T89VR*V%S*6dP&TDKQ;=t8u@FHu1Q9e;;gaPh;Cve*gG!sc za-=W`q1;~DILvjpUtk69u&=$PRZV#?qA~x*Q0f&VvPEolq!CP6yvt^?$(y>+-R)4V z#sU`kQd8tFXcqiz1fhE0rmDe!jONJ7`%v%{!z7hYe z`c*9@pn6O-eX~>nWx44nm2S#&T`Bpe-wUC>86q{5dXH0Dx&$D-7d%#*fhY5*6Iwj?qx`siX;Y(bxkL6!IJ{g8@ZB2tFA45**mGqZHlDFx}SHw~>a-cr> z?pq3#l#Cwbu0R{o z?MCV=$_h;=?}z3>PmYL8tlSrs6@;I-q^xl49Mud)GGWP^-ZG-XQkHH}uByw3kS~rO z_G1rXL>T1hA*JnzGYC!(AL}7MLw+|lv%~Sl*p?=%Y6KjHXKllWgGx-`!-u0V3JX3A zw0dLRi+iL{A0$2;Pxi)w-G_n9*@v@+XG0Ya2bsik#D}59OC{r{N ziL2jT%Y!~Z?kp+*5g#;K+!u)qR)*N}u+SV`+S?zOmgPdpg@9`+1_vE?I64%GEa@Mt zrSNs~(z3YX4uVAvE{A#0YoKdFQXmxU?un-aa+QB6AB=ZLBZHQKAVC4doLwD@2upGz zMERwK;y|y&f`hj?Op8!Rar2A}Oc#^H>nS1cRXbj9xL<~lAM&_qLfVoSNtL*^? zf8d87ga$PSX4;@=V9mq}3;!%5Fc5={)3;E`@jFFMsp%_w&qtP6c3b0s{ zD+=nxg~AG#X}z}$SitJEG}Q5)=%D}%6 z{&rpMvlj2U@=xX97vS%NF}{tyi8me8fIkX}< z{Brm!HZddK)jzVl2>ic;zuQLbZhYwDuXauV|M~ED!Rd|bCOq@jY2(3vJN$LGgo5v{ zUp~45{7=B&BNM03Jjqt_r5^mRz+cVtYd4*?t$al>_}_xR55Bx>OWZL3z^}poG0v~) zwuc(e;eTBV{u0D6Xwj$5x2=9_bqRzUAw2H)kN$J_DX%Xpg|HjKooAzK`tGXv@kj`_ zL3qw;$0vX4y=@ZuM?Q{hEjRW9xa9fRl3-0M-80L6zzZ(fK zaP{{={lH!NYg*=_28L28zT?dXU1GKShiUrjAEx7H`@@v} zyK?v^aE}4^$ZZVc!Q*8cE!R2QXnAhkM)7uTqxJX*+)22fA7Pj#aIKHf{1!h#<1d!O zcRxbQ^$NHrg8P4Rc-Et|JZC;i%X`_QbX@#@#9awkl~wxZvM3@5iW{3K;fAP)nB)ei zD1x}*g1Z*3xqxMw3*oLIF1h7a?ueOCS~(>yrR`JBlr^pMw`D9Fv&p8KO8xV@=X~Dt ze($+F-{tbmFwZ0fzGlH(q8NolZVsPp3`xz0(n=1aDh4?%p)_U!;LSKRW+}TmA z`R^+RIi%vVxB{P6@%f*#Lf*F(%=0TgC*1Afu>k8A2|?x$jQf;tMgFSz-x8a^f~H*lQBQyafgd?5f1tKzES9f;}BNrqvQyFPnody}rP1dpPJm zme^QNhb`S33+!ph+Yx@1SYL^Cz?ujVShA;s&NEbCKT10IV85iBs9>EW-9w6B(i#bD zyuZNiMqa5Wu=_k5Sm$u*Q*Q@0wY9*Wl^E7aP#b|oE7(gB0t-{Hhucb@`UtEdReG$C z6WC=B$Jin|2rR|RF&{G|_L4G3&q{e8Nh}_Ikz?B~vCb9$4@(r--#i??Z*h;n_INsc z^-ec|eX00wP6&vmKcNi}G+_w}lJ|LuAy@o4U0|~$hP>IQg}{E38021%G1NfXf_ySx&f5l?gLm{mIu7Xl(;$Jx zOANW8-e7^vl^F8LB8i<;#<4~E^ffte$e(XS3hZ%jhmN?O0$ZfS+U78U9k)5;6-&A? zHV54qsbh=eMb5sEE$JkNIx>Ewzy^Ce#!))a!`}TEbjYV`QKTz6C0`GRuZmhw40_Mk zq8Refb`yqNccK}^;DZJ+6oYSP#&F)3V=0Dt`PqbF&0J~C>7J@jG32$WQQVfxwfWdi z*5-8HshsW=d-C%2$nkXeG@+L8skagb{otGm#9#4MU>AY;DzR3Yga7ih$D)C~Coz>S zIg4ZA8tm609D84b?bTp)a>O`PtT0E|9Hd}=lLXdZS(oih7-Dj%J;*$J_g3cjRr^3a zlarNo^q!xfJD{w$agl;BIeUHI37q$Pjd^6}{&YX&gXr)+A~l zYF=7^h)I7XN1U%qG1S_-?-H01Yu!c1Kn7w2^QDKSdF~l8hJ&Q??3bX>6Lm)#n9sTz6TSd=%%qCBg9p6|(Z_(>jG;7*j&m9vCp0>OY*o*HPifXdibls)H~OKL z7W4Q?i|hA2jm=#&^1ihwuc6JFyzL%UUq{btbnMsY_@^7SY`7-I$5gqu_4C-Qu{ld4 z?^!ou{0EH=AI-Xqu~j{{kJs2->_)AqSLJgM>++^pl&9;Fx@zRDvp7dnH98*D=y*Y+ zBf?@H%GKy-<;8V;s?pKRjW}ps<-Md{UZ5NE_NK*t{FK#vps9INHGSO{&Dy$V$*219 zhP!bt5M|LCH#%tsyYve6-LtZ0|ym{5! zgFI+4x9@Y~nTXDoc#XXK+&C+!|6h{+Z$ZJr|_SLV%KD_5qV6(p?V!K~r zcy3?MOJIgP`}0r}hUWt3B{o`O&=ut^FyomLYV%Qv8S4l>Yv{x2dihW~)S%NQI^_Lp zCJcSdlo}Mn7M{cBPTGbGklVu=4YC1&)0ctznpU+OE2 zw^OjdmMhqZ<{aB)!jQ{0^%2-362p6Lb;bzH$Q!6%FGy^f#O&|7NNlb$ZvkTkw%+ET zOOe<%g>KPUDi3SB%-(?c-l-_?-^Bod8TkNp_`1Z5+Ktcpbp#fn=xA79V2KLWyEerz zzpMQP)>QG!0g1I$uuCQky=J!{fu$)i^n5*m#VYY~QDQA6h92#Ei5dMQo~=*oEHIO z#a04~ROH=|SQEw14O&w_gT7yD?$bvjs1Gn7#Ulikpy&!aH;r)d5?k4WWL)YeGwgao?8> zqvL?y1K9#I`d$2PSU-V9DRHo{A6?V1>Bn?|4Uri3Nm&B~)>*;MX9}#nl6RBEdpurs zl^8l0B(NZ*28@+-HPtmJ>AEU;vW~s6^0XP15OuSw34^G61$O6A5o~!t(iwWO#5V{m zHCo|4ZsG;DPhcr=im%Q~dG(a~@uf)~##VbA#o&WqOg1CG1y2xIurjul!Q8%%6R3UA zySbjAGvW_>_vaf6IxhvAVZxBEpN^vPkV<Xd#N=v;8P8{s zS5u^pFeOeKNvw&&`%N>-i}$bQNvwmCOMa6&jC_Ul*-_G|^<&Tk?&ldAKhJK?{k&G= z=e%g@XXx0i(eWpZjuRRkA8B-~FzJB2s~UNJ6RAA#jy3TjUQ@-pZ(ar+;{TJj0yAtz z>Z>8?Ix2n6caknp^=SvXei1`y34FZ`yN9lQ=-(l+#>$+ZzK6~^=>DBV=^$fk5{)&) z&|66~hQM2##C6Q?Np(QwTfJ!v;XL>o@m`FLVpN*85(MO=f zEb1??I7RQm45}BYdH-O66)4#667%+S#CYUH8sm_A-$XjUnEMXd0&A+w+op+h-cXkc zMhYxKS*yoK(zS|Z`mw|sDn9*VB=sqL(PosuLY4J9cobc~uzj7xVimeqO>`)|UZVw; zpu}Xa(KIGu`?%3G*5H?gqp4p&_mqhaar3&w>M1q(9}){!Y+l}*u5-k}l?+~wmQA7< z#&|?xkxHNVfy9hBKCrPX)Mz%lc_w+%PSJArHt((iPcr=(Jv+)7`NXP zfwfXD|FrwU$Q zrI+s@u>i&9jH#UWdLXq0*eH#>sTz5Kjk&xgjj24$`O>L$&asCrk(j^2`?ASC=(-WZ z=l7yX9_I4aR63Vf6XDYY)>2tVgQig($U{XEi&OI28tEl>Dchu=YATy=*Q>WPkAx6;`=Et-1Ld$jZpHg&kV|ob(uJW^1?6E z1!gnGfp)dn#EW$O(+oOZ%t!J}!P`vPyUdzNdEwK8GpSzWt4}0v9i>M7EU`vPyoAo8 z@(^YnXHj{e%a}#u3Trk;Vqr?ITRV&Qoh2rD=ov0Z-kM5X_()>m%HG~{HkF6Zma}Qz zz&gs9P1g}*Op;ijQqPx| zUbF^-<_g{c65D~^VlL&yyCk%4%s1;)*mLxHh&%=*Uc_`a;3#dG-)rSk{TE*BlO3avZgx<#m zR35^1z!ExMteJC5=$gSf^k)(?{$2yaid#zM!JWsJ(m6+6YO_r6`YC?yDKXSn7x9|2io)166>t+zP*CV!&bWON;)6# zU(rhHKm4vlVs|NZ@S~Ny4&E}!LmrP>Me{iNh;FNBA7Q_zVHK5!{Z`Q`Di3-3L&;l1 z@#)o7)TgKq!K-<1+iW%OZD+OOz3t*wyjSjOk_VE0t7*T1qs}~u8SgmYk->%4R0rN| zi(12DEq@Ii2lhu>*3kVC_MGpoq4MCv)Ck zeJFT4ZRS1eq|J2Apk}O)SUY9BXE#$H;9SLf3-tkb`)=X9OSf>|S53U=|7vZe{U1_L z&Q{8c)OT$wUHjmjyN&Z++{SrFK2CY+E5ytDJNbU!V;A4=ciP3zjHd0PYacPP zWEb6=A}1d)$-`dbb&b5QHS%t1veh?r^u`a4YT(<#5--T|a)` z|Gmxbyh*Z>Y?6`8%+<*<%o}fPKIQGKT=4u;t|w(k&3$CW=mIo zUxXEXJ<){p=yg5e8RLC&C6R?^b+mI!Zz@@b=aup6evcN@XH=GmYu5J=`Gqnxok5m} z>BAd$q~A%R&3UBTza7GKuRL2rmJk>V^0ryRJFDgp?(cJbqNme~mhrZ^rd>B>lkD!h z8f6d>xEJnc(B+bHTL6H_tzO3dlfQ{b`>r5uf_7+)piki+c$97Dc$W*uzw7~;0)bq* zCeJ_F9_~r1*y4)rua}%rBH6$KrY&7xcHPzHEK*dT!SdN;(zd;lWGN@kTjlQ_^lv}C z_9K0a>P@~@yjfnoCu|c`IFd3&Z4u;eiH>ntCS}HUPI3so;u9jGbPtwfqVWqJ#dLXK zOU#bkrd{*ZaSp!U5|kWmR_f9#SgYGoHM~D34#@BRu(MGZd$RMk9oXwS_U&OQ#Dyp{ zZP944E5W2?C|KV9R2Zt&>uU`SC*$Aprqf+H>EFr=<;;^Tj1>E_r;0fSn&c&(_D^Dd zk=b~>&6hcLShwUn4)S%8M1AR0_Y8r-f(=>U<~abUMa>~<8!%mI+qZkmdC}$&)k}BL z<`*@qv3qm<(UTKNG)QqTmCMO7T|F(^(L9O08rZ{ympCaB{qzWAEw2;uddy3dVDhK4 zHG^KmR`7Isz4lLEz|b*)&%^RMK|N-CM+pA>s2J3PjV_+F$cgt4s&dXWz0RF-d;SLX z6SqjSbCFYzC7pMk-y~6o&bQB&XU3JT*0g_9QnQr}RHsuO8XMWI)agRGs@vz*`>L6d z^$mS#l-;z0qH*(_(QHC6hRPK;rhRt|4ena7|NZ%4H$>IxZR3m6!0{SqHx2npov8`t z@3RzkZfHAPeaArDb+=3K7tzd>7+cSh*eB&gUT^P7K0?(o*wxU&W_&S^R+YN8xNW

|LH+mo3@zB$%`4Us|(8>8I$PbFIT@;93x4GfN6S|Fa|Ij-w67453 z9@^J?B;Z1aROX>~)E;1a`)?bzaeDnzH0}8i*6o;X-|gtJBXV-;TiAIfawZIq>!>E< z|97h_b_9Cs)Wm7f@a)9*k8Y`NkqJV9c{&MurLB*9GzFYe8m9jnRHpeGll9pBK9|vbI5W0Kdm)$6PM42? zUdrn)*a*Q>lhIg9o7cs%A2*uGr|CA9JuIxpXS|NamFLQo%oxZ(Y1|uGUaiafjd*yz zI;&&nQkh5*#>c62tBc7$qd7F%1;;K$&&CU%>Tv6CJ!vFh-Jc}d^8-eA=4S3MRnP4( z&U8shyPcP0iOjvPLA;VGo@Or!eAmPVyF%@7q$P{k@4d$g$a8vhq<|{Q5QJO6tb2R>kuBFbM|$ zT2hGL6Y<)A<+0b7Hp$XY7Ux+KOthPh<PqAOlLggw;uSFr{McLig;-{Z0W@-kHy!qO$$4NYrmTtr1 zj5Kjx#6E{A6v=qTJywQzi4%DM7~h=S$l5_Eh|Rz8k1i$5@AT%QTN$;Oa+5OpR;?D z4=DBQtlYVjHa**UjGxDrGe7;(Kwwcn(}V_7I}GbMjL!2YaTHQdLf`W5sCRxG;qK0G z>g{*ZjB`NZ#{{75u*&kFI+cCT;P=E`%dmVq_-Oas?k0M-v#fF4c6rof->IA3W8(aD ze^?9RJ*>TcN;^aHJ@M4>%teE@$@7I(uD&Ntu``m|&bFRs;MitML zY-hS%DaoA|JHm&IY)q^>(G+>pXPXK$mEcq_kkr%YF@yQN=yfaSpgEn^V`e4qI}DL_ z!7AyRfp+bkb7w=2Qu(pCw8D|g+XX0vbBw^$?8tY!sTJd_q2ZJ7Q(DG%{3W^Q!`Cxu zZF_tFB9;_-g!O|xU=Jj>4Y3^*J7V;kba-OE@&3%P1|<7pCkU(qo$rDZJ8J#=&h=mdR$cz_N6G~ zZa4J$QylB+0_j;N{I^PQP&P0gk>GbO-S+T9Rj^6=tUT~=j%c5i>+sh@CM zZE(k|iaz+~3dMZS=%C2Z*wJVjQA#(OF}NJ>y=297OBySPQ({f85g3C{*A!b!^%sPU z90<_Zc>cT4eEOge-N8aqLb~c)C)ZO#^Fhj|GG+}rsLt6)eO3}!5>Wz9DwBqd|*$?XFY9Fm~;Sqpe@=KTaeLsl5t81vq zUw%Wy&)F{S3Ddlu$&KWBQ_RRxo>o)t%zVW?5w`{if@?WuXDQ`oOn3L${ zx3Q9vqFm3CdJTWd0=W`3tH3mfw1yDlj6_mY?jZITKK;a$!ODmdo{|776r;Lv=0WqM z2#x#iH6mc;QkP#Oa~h$8>OE&7iK<%~=o$tys=qYEXSC~NEl3Lf667anyumHz`=zlp zvZJc1p{em*eCAV~Do0YK66TEBFD?60~6wv~tEOay9Lkq_AGyZevsd zQz};zLBdo1gcAKk>)i6%%KPHbfbI)b`F#lcF_7a7%q!(CWC@Z=ZS^r(|Lj8#x z$cxrx6UYrWr)Hl2+QXMl@yD7dG)&TX&4@TW$qHw-bmU(?;c2-@@WwvzMZu(DC%tXbHGZbP^e|ll>p7eNN*Vz!hT8%r(3nfo+#|2 zz{bbR*K`Zs5se?P39McxZ$$e2;GdoEp10|Ct-~@}c)O7^;->IOr2H>KDy=EldY_bM z8nuDUDrLCbH?+hU#k*n$7g@3k^A9nTYPvepw-)v034SpcX zHio6ehS{(~!@CD<%G9k1Dg}(GV5J8CN)xJ#Xf)+#UT1u>Y)~IR^+D^kO|mf_q-Z}TOLDRGPC zAao#ItWMi#Sqk^Jd(UAf2J_dirfh%qeolAG3|II|khji0Ode{@ef+@lb>+9L0f2!R zRKK;$#$%ec4`E1ykifxAO<=M+WA0F6K4wNV-XFcP-Z2W^V9g)zG>!Qo)fgYOBenm0 zEOX>Oww^Ub6(~{SHU+?d?K1*4eYl6+DgW{xbzXCuCJ;Jr_Qx~H$2`k^7VTN`~acM>WTygZv%s_?%ML@W2l9l{#V&g^xX7L z6yD@ixwB7>S2Jwz)0s0K8!vuEtkI44*!kR%Yv+ zAOe@@^R53Q9@%^VM2ZnrKK$^JOk7kmPh>y0*b2wUMb;i0fF#F=HWZIk9}Cc3<&o|U zPox~%Srd@Td%wQQtM*gqYJ*u(U!5_TSB!3oO`Td3Kw19-!K zrS*GhYD4|P6ws%8hfZWw(0;H>;|G=Z1G-cIhBDMg9lz2PEW9N~syQD~`R>1O+G2Os z_dim2M9*>KgbM`+P8AEfW1c$_OG56jB_iX)x|DvdC#5p}=0+`M5r~AEvY1rU9M#I| zt}C&3Tk3aWIrqza{X}iIjtn1pr&5KsvXSpGEveu4=N1~N8b2Tu(rKLM7SMkTB#P1-9$>Ws4~kNud0xK{UpPeRM<#_(OX81deYvIy zM_~kNW*hwoOG{u=@B|yHXqmn&HISHtFbZZ3Fo&G##(V4%u$_@it`YV*GOCDeMIxb9 z6xwiGqarBJ1;aA{>*iSpu()eI*|zRP+s!rC$_OlM-u8&ru3 z5OF57&k9}N2E?$|dx+s;`!uV(m;=W$jE%U?Ssmx*XRCPA>A5d%uTHTe6pOnZ12=@< z)u526A=tP47k6as^yx?W;-$%o@ltM{Nk$nYr)DPyB%$qtPily?{+WYK1kT_i^W`dO zUm!bQD~A)dZ+qOMLYs_tdji+17PiTKT8~(%_VBVEEsAN1;HS~HqewvRH7dMAW-^FV zhmp=a)dHwk(x9ENGLxX(aA-cDc$BQ)MW%8}Wg*6XJ290Y*2odwDOi-)mw?I=HKXz`H1}x_RqYT%d+gRJ3QQp>o?ywq(I-2$1BL~ zs3!+A(!MUzhbM4foWg$c-`#5Sl$E`!9nwiq8%UrS)8#2Bc_$hd7+LgvB%x_H@YQgU z-GFC{QC9*^ztL2}*>0>)k=jr~Kts@79mihuh`axF788f|nOI*W)31II8M|e)#Gi1P z8wxzNwu*PC_<{L17>x(<%r_=z`!n@<+ltAiG%Gs-R^%6>P z_Ikc1`bMAq75{2yJ&-)d!FnJ1E(Gp;2wYcegLI@PM@fH(_(d;4E#DfG+!E7zvJ0S8 zBP(^p;a#bDOQ0dDH%Umh{&vuXp^_kKsAzd6;q*`B%88SpL}U`n;!;9|b`aY4L*oJZ z!){}~{m+>lJ}svL_r2(eK79#xulfkwuWIrr;i=iuXZH{u2JO%*iu|Ck>)0_+6fvPDbi^CO zhHau_O9?46J>x5;F_E+O-_zd9NDlusPNcOO5d9PQ_Gixe{U$m}HRbzwF8?tTBy&?# z+`ks&mlWR!LX*OJNc6in7DIflL_Bg+rL%eFYC|LfmEq!621UQa}!k9s>RO^=iqlLxo`J#ctaWSYpW^m z;{#&o)MvFri63GHnFdhHC`KC58+-9X$tGSOGm)3Zg&D1iCkuqJ{)yaD`1?AKX-*!h z;SR>nlIQx`D?;7vs4ti0IE^|(^$j0HBZN73+8tuzPJv3`AB!J?Dip|D#q5Cl*V!FC zh?~Y}oiS^ByPo0Blu}@Wg-6wqFk8xRSc;KzJ#}Cc-E-A+SRNafiHP=_5ZRl!HeJ>;7y(8h?N!pGo|?=uVS-$vA~Dk@8s+t-$Is@j-<~a zj8hzz{!*|0%H{nv#n_YfLNj?M!1Ur$PgwKDnBZzv>nC`P9WJu{<6{gXs)o6*OMPjnP0e$c|!nFl#)@<`-Nv6LCEO)6~N zQF95RSiGb4`T^DNpx>~&o!&AKT{0El{|U+e7n@#$FDslY+RH$?a6%-OtS$!(EO5tp zwokoROKTbfp(~PLxA*9_=|-a(r1O$DO$uN^pY^Jd49Or>%oBPIeE#Cg&u|Ef#9tVi?hI+F4_;SrM- z^}U=KU)QUz9elwith2`N)#D;cSxCpkDXnr|d}xU`86yR96HF0==4*p8|AClD>D>X`lWksz~F9)|6cdbaa5r5yFTBp<-9s7f_CV8K5%6a+Ls=s{%bn&AhqV0=bH=~oOS z5irQ6)<7)&+4+oS%Y$dU!iq5On8)&c#(Bf9?1~nn>`KYQtAD%}+Zn0C&fCMO9@20t z1ux#b))eps{XH8eE)GJpY=u|%HYa!mQ2d_io`??5XD#fDb6vpAfo zgegbmhSqbW+^cZEPyiRd-1NKQCsGk>#qm!&(zvlT6zPz@pMeP5AvpJqfq9=pnZ#8 zQL6^N0rJFi4S}q18-t{DY#@*Ufa&o^XX?)u zA4TyT>6`+>ctvn^!-j&|V!^wpm8^5#M|k=eP&A-Nf5%m{Qb>=`!}p=PdPs4|th{b= zGci2vad=ny%aW(3ZQxWqZ^ZvV))|JY7KGttcg!#6r2?`o3I^g(iC!d+McN$lk=1&= zgT9mG7H=7n8_3FUkaHrpRrnZr`mTLbVzx#T^0INW-ibAZ%G*tvZSFhl`mK!UdtGy? zeqK&9QOz`DQ}*f59vfVKd~ZM3i2^uReZkF08+|+PO04(2mE;+M&J7%mAhRL8Q=?io zVQE!8rwffaE57~#u?5<+8@d3I>cI5=&~L<_;H-Fm2)}jSqbjUd8r$BZ$9Jbny**uf zNfg+vQHDWSjNx5U)1jcCqE^KqpwSJ}=6C}#oP%}|X<)VvIl|YVSJe~6-W)X##E+xu zC27Hez;>19%Vnq$<)CDo$F&E>V4nTO5Cqg1!0|6tgTX(+WP$+k3m%&;wkjMtL4fBC z*CswN7@j`+YEQ6|AYuJ`Xbg#DAHTHvSDRqC!#Aes<;Li7q5D@(H`N4`7C7JHZ1z|c zV9GCvG1O2>J&%6ALQ#sN^otZndX~u*RjZt*d_-dk^ThyhUZw`Cf~cr5RayZ=0IDJj z7iJ`}EcE*@sZ1ebS)QI00d+R|U^TD*ujaOx1>V9!BAdSQMHYVh;iZ&@>2#=Fv>MeN zpB!8`_v7TKS#-Q>0dL~$#=955FpxtJUGEB`451gAthVSYDTH;+d#PwYU|64BY$UCP z06ip(UA5|EO*7{Z2M?995Ko%IAZDzK&^o&y!6)uDa`4+M;w3FV@RocOyS? zCh--S#1f{c@oRraxE!aXm87WM%<`5z!lGU5-@LSFw$!j>a~fkmX#~I*ZDMKIsPK2u z?4+cg8MOXitY$xtAD84*E%*K6(fO)ABvW%k)9CMlq9srcCF8ZBMqiapB*}n!8a3$_jwC>zL!kyUiru z9zf|1dhVhjOYdLQxh}}L`D9FFA9bbuE^;N7*W`Vu&3(Qy?Z+dPAi|MK%6-Q3@@c;8 zG5&;Y#YF1cw%ZpSl@cVwKOuz`xBAXpx+s9xTre60Zuc&F?11P!nfaJ43n zH4nNPm&iavbd8Kl*!XX($q3Q*{uMSt-FaX_=8H87fL_L3<>bu${#wg5=v~CsD+T`? zl`JvI9Ws$+(q7-mG5k~|$OXCrlTNl_A-XtU?uQEY=?}O9r%g<1MKMMtk83!Hm4$2= zb3^fnP>Ns?FYhd@aL<^xMDWQsTrv#u%M#yq-xyGL%@7Nvg5uqdOH4DzG3DeYtH{7* zt6&;&0z`5`W1(uZ1aVgc@RlrOq=837hjF0_Dp znMijBByb;E9C0qi7Y+R()j`(=bh)go(D?WDveWR}=n3Tl8j|xr1rgakKcXzH@EzUb zz(3cP{i#FwKILvkU;rPKL!fmys#IA&S#XN+D02i(fa(tv>1;ba+Fg^hZzhl>ru*hP zoG*45nkKq%x41uh$q+kcAIda0x&0y58GYTJYUD(y?^wdoz2=KGGV*?H zuS*f%2Mdxd?egA6#zInhEMfN-2B?@5%h{jCw9>I$hca_d(D^sz?q&4pmYU5zmJw3< z)6(kxG*g_7z^vLyo^2wQQoVS!mE1zlfL(<6yBvisUPHHk70DX&l@rakleG_8+->qT z*qts5PGy&14bMzavRPO4a&n!U*`7wLUc1d>{Lbz^sLP zTzMg0CZz$&nD%iH)ZVLnWU0?rv}-U#vsS{+=H55M0IT;iv~rHjMzwuP*A^`AmILH!3TKHJ(e+g%J#=X zc{kcO5ziS5aD4IJOI?3c#%s#Sy`~jcP9N4auZ}VoiS^HO0=pN!uINZCmVJym!CGY& zNq@)qJKDSPc$M{rYn(EK-Q|shDx@nKZZ3WCEll9dE4nakXIEzR_JNo#V*cwn_>YqR zfSklIm~`DCI7Fz3!Ww2nl%78K`q7ufZ{@GKF{j=R>jKkQtk6Z^`He}~Xvq8Ra|VRG zFQ1++X572o4p&Tl2z0wVn5e=QK`ucqY z+#Mu~EmgTLv?4hG<{kbrWFZc4;~F)d4O^$P>iKB9VvFC31-sUp#kgDkNqX@iwDwn+ zcTq7dbUAb&`M^D|x+vD;A$alwX@L47>csmTKN#hm#c}8EoLihdTLET7r5h9<)J7Wd zDv>AA>6?8(uBY(kJ@e*0jg0+l{I{%-Zkx+oxV7`%q1jpIV-bupR2}WaQhR}Ndng|7 zlJtD*)#LE9mOc3{^(Y0mVe894cAI}fwHV@rjec8%AnTjzb@5-3&yrnOZHLEo6)zf* zNn(C#6a3@{TG5%e1vg+I=*T+DR_ExO2p|je@c3vDg)&+f^xar3P~Q6VTuZWs+oJaJ z&uk7Wa`&|(sgY!7H~<63r52>!Q{DD+oiaM3VbREdg7YcckYcnxt~;gX;aAfT_fnUq zqmVvV%i>FP-MM7H&blq$q8GmA!z9`=firK$i{vEQ6tzns}!e6;#bx_SG|of_$6 zrVZ)x`?xuZFQ#i`LW1d-<9D;Ty@>m+1|J=ya{Kx&n}0ZQ$L9TWZy=WlZt{`iPUX42 zDe5ch)GOCM&L>^C`js4k&TudHPlE@0Q|b=3kF zm*6a}%Z}?e4LmPDx=^RILNo5<(<&I}(VImD{10?STmgd4#JQs8J>pLa1lOZZu~&Zl zc%GQU1y~ZTV|HCJKrGygYi4Sla?uUKw6TP}Q6_BhA88;jF-F!MTHNKl_yDJwbC$Fh z2lG^4<6NuoE+0bhY0xLwp3b!2S4sAEW&N zClbn>=Z(NR;lx|H3&IY9IMi!6%K}7jbDZndIV=d6h#dq?k%CuOnax!tPn17>`N!kE zvZvV_aMpPoKz#&?Uj{L(6p|Z>6dR)4wh@25@_u^NFeXAfnT2jy8In5S_V@ORJ;Q!v z)ms#0wBAWU6m`-r7%C|PuU(_2#N07QGWVrJ<88n1$fA2S0mg4dgP78L@&gK``LHL_ z#iNR-NlRZxlX}z_!a)pBSGE^i7U0RHspzDi3`fV*Q)K7C;Y*#+=L#)fP3kS zuXwulBk=TnEql{b$6g<%rc@u=AqGhQvI`!se|GXXBA53E)u^(SdA$AXKo6K=ICJ83 zn#hbCYGK2psJtm6o}PfjX> zd0jq5&s;U)Ey+FzmLb|DTx3q&G^r5q>snRYhs@UB>sdzB=HLa0e2qq$_vLS6Tht!~ zUBXqcNaLq2vohFy&)DZGSXGGK@*=i%^PKdxBkj++iQ0#;zwmu_7cUe3GK4tRQt{27 znJX^W8i@7%h;%)MYZv?qR8QT|cad}B5k@yNOR@}o39s&$fpTngVNo~XC9=~Am%E*4 zHqz>VS6t@Kb$rmVy$Bt$t%6}b?Aq-!Rl#;1c6;|D%LF`SH-2d^fP63HHY}=O_K&+; zz1iBfo5g4^v;{Nrh394fw@khgQY-7gxy|>CP;9Gq^}RTD{886@R`Hax%@ zgrLE)ti+{PGQ_m(LZ0pul!7;-056s>E$MJ_JWk){`Oq#udAy!%BmD&1@ix&E^j^8~TG~X9 zye=8G5d300@)KwsVE!@DK2kJDkkv0yk_{>Owwju=SM;rnCzsAcfb(w!`-_49w#P0`Gi;Mv9*-!6^=T)gVcN zToj1LxGUVS<-Gs_;Y3|#XBP_3EE+ur=LW2^R&q!psLw*Mhv)W?S!r;7iJmmSKEl~& z_c8-N*I>2Q0*sYUmEc4Qi;~@&E+LcA+dae&bz-Fjsf&?mMc9yiQBMTU{~ipNQKxMLg!-R8PFm;z$X4vVVX`)s+vUVEF8CYw z_CAE0>x%VCU&t}jB)b?yP_E2b4@Y=<1Kz|00EIW2m9i-tZ`7~M&^JC|y8mX-?YU96 zGs4-p&y8tl5JGdYi39UetYmv%iF$FixpWx|Y<#*-I0CWI%~*>KkIbzuQkTfrCD}(^ zaypcq*WGK;={8HA=W2L@#`NX}sA% zEZHG1?n|ZZlDL93ZyE74Z@&YRQabrzbkM=2zyaMqCO~7(s~^S~QdrKoP;Mn~h~*oO z;U7Tb(E!qd*Dr`4V^cW$fYwrzf9Fg;#*7ZZJ8s;^U%bA`j_a|8F26B~9Sb=){jzI- z6L9cxsaVktkkmo&Ye0fhE!6>F#w-p=>m-=Ljww19iz*@%g2?^-U#3hB$LF>kt^je# zH~Thkr{_H!Z=Eue?o^{(kf%3`#H}Yol68TH`bB*)STy;4xVaqWEK5OaPoq7bt$p~W z@67&p$)|<-rec;GA=mkOOu{L!>YN)ft@#Lab3cz$@`GlJgrRaIrEcIF4!Hk|?qGDozDNcOz5hGwVAOzrghkfnV=UUhWrKMgqPW_2BKuPA zCDW#XOaIMv2=Ua`{a^hTRETB;hHj&oX3PBF@vvG8REt`d}VI zcT8a1GbV8MTpB38@PZ)K9a98fwvg+N$>dy$q3yfXk*KtLCq|vG3LEYjlU4-OR!Jd^ z{LOM8jR~ne;or6XE@foOan65Hq->YhhAY2+>`(wD|H%~AF6Rk;9#zT# zZ7Ll#a9DRbi+b;i=17%-0%$R;H$<$FcJxq<);ZEi>RU$`R+1~~RC8%`ypGQnxMUmo zJW`IZnA0ER9F$>F=|#kR%zCHPWT)pp3RXh7F+G|<%ia@YDi+kOcvdw8(?6Mt282Cb zYlk-j^LpRzMXhkX6&of8 zx78VXwL~UPY-Hg*dc1Q}ERKD_QdAr0lgH~zmBRBG*O)_b%9n0xp7mbA!d82T<$WQI zH3e8+CiC4KPo-v1B^jpBfoxlVYoMm7(QZoH@my40R81Ui;;u2qAYxv+-_3E-r={Dz z-E7s0!lR;O2MZ^0fzb2$%ssSiRcob00btQ!Hngf7cxGIa+6UNOD}$vx>?((hS@>!x z=lakOww8h&zCQhYC+cf-n(igOgh3LNWzsnDd+i1>^Z*i}X?B`u+pjm(5~jh18^;}+ zRSEX-B=(ND&o6%tB|f9!uucmYteo7THg<^P;@Qmj35~igAR)8GnOMeGg2rxKn+x`9 z-OgBZ2*NS1WF=?qEAI~H(6x>FQ;wZ*d4OEO;@<~vCdE6#qpomhh=zohS4ER1Carvf z63bf|PNNAC_Bm%|oI9U_y2PWn%H7X;*sXIq*QCM?f99z0;6l@ejyJT8D4=J}c$yu% zks|$TB%c>La(t8^45&V2PO8TNE_^`2&llgV?KkF*dF?-25l_KFfsow0`G!(It$wXL zxmhNRwB4eDl#8*@1HIQW&KI03BBMo0vLRj+MczyA3X40}z6j_Uf_VMK=HARM*TwKh z}y4+5J{R{BD0@n!r&{b zszaKg(Qv{BtK3v)OzUIJ^&M0~Ta$5#w-dQIZRcQO9=-mid0?gI)U_e155*S-Bg;XG zAJZ_5FQ*uKwF(hWG=SiLFRdxC@`f;WS6q0TYWyj|^Bbo~&G& z2!SWjR_>iN-odsHHzj;JJXDi{WyRj^GW}E{!S=TuSFU(`beodIxjvxM^K6=Thdd}* zuOf8Zi9cABPFj)PlLXLun9wAJJBTVg@09BU_SqfdYn(51li1dEr=8j~cb7;!aE~F# zHT#gg`Wdw~0ionv=c?cQwgjQ8oqjh6KgTe-ge@?mW+GdE^D69=vvuo&PqXbj3*3|l z7C(m0X8YF-RO;;LTTb`(=`hg^b^`OXTtY_!Sm@Nqq%TnL)DpAB$~+~zG%Dk3_Ynf) zzgKaa@u-TS*VO&M=Yu)XYyu39*oN_I_D!ll;omrLwzW6Ky_@0~94n1eZkBdmRx`e? z*x8~^NQcFQ?)s8S|30`Sq4my9qU}=_WC^L!-??96xrX^&r(663G^2Yiw2Lge#z?)W z2f|8Xv9RH+Rf72;1C7%;t*>Kvw~qO07NO3oH$Es|PQ3$}K9E!>2WR0;MB_ichG1&H zLcE&Tl%tc7e@5x`^9ksdUm4vfEgNbLg} z1f=~+92xSW$RPwA2k*9#x4VB;$30V1HMw-MGa8D%3QF5FJ@X<*&&X}pOg{%piK1Mk z*JPEy+)rC*w70Z1OiC1e6w6F}j0b2RXsbGV{vqhfScrM6Ue?);7nmL7eLzV4`~G$v zjLP6C{GSBF8Gi|70?_;Ofme}h=O+%zDI+=cBYVXPXv0Zfdj5NscH2ty`uWJzKka9( z#rlW!Lp^0dpyJw}M75y|H4Fs518#A@Wp_?10n81s?mZBdaHKp&gd|>B2lr0%HPT(eXT}E&1jP`0y?^ znMxezj&gA-|GHu{$kd1YoZod{Ta4%j%poUNW(dCY8Z>R+XAmrAJ}0j54k{~BGxK!9Gj zBxv>*C`hW*WV{PoSE3fzpZ5TXyp!IAAJDA#iUvhZ0+n-KzIN-N-3)h4$epYVGhDf9 zk!38(nu6OW*Wl{jDQH!jZrGf3!VsHE4l+d{b91l$@veK z1-OMF%NZus%+0!z<%Db@lNkG1TK0G z(7MZ@U7@fSU(kK{kLVCgOngr^K(;@Safjoybr=@VZ9Bja(9JRYs|xlSKXY^KVMLLy zoy5le{L^~j>S^n`ox?@(&2bfnw%|yeI*_lwI~98B8oQ5o)Z7Hd3*b#NAjq+aPu*E>3b0?Pb~( zquhd_sy^dHIyQVFD*2((F}blV-e&F#v{6b_77|P8eS7fi0wIszbqG49Aw>KfABBCz ziyg;gSdo9LYkJ~?ac1x$U&6kvNZdsUI@XIjZjL+di#xU;G_W{XdUN}PB>bQS5yi$m zV=Ir6k&(2TfAa0F4+qpf*tN~a+3WRRtM*@W%UYixkZ-e)ZnIEsvp!68VysBT9pk?k zyr%F4-M$<7;rp4lAtC8~rqLw!_Wz@3P+8i8ug?0UbhC_VvXk6>2Yg8e>n~vifUboYP-Orlj6JVG`_#yqI%ly+^=u3YIAp$AaAm@`|l1$y0!6io>lOq z(x&OS+A}-Yht_n7$Ut8~%of+;30fpMHGI1tI@E@@u?K04EI&c@m;63?{S(f>` zBin2{M6-cJ4BNH8u2%hYA==AC>gRw;xac&3TlC&{28&U4y9zn$5iWD8%XFYPUY1ig!p~zfIGvo?mP-E2pp(^ohy( z1{`-ahRTb4jN6Hdhj;NDAK`l@m}PMjfFuYXXU2ohhGV&bV{yBkzJdsyGP9-2*X?Yj zkDvOC?5)N>__cPg4k6wqVM)D^I{~|=_&z9*E{@==9r<0fu+h-nb9^5%$iVz}E)Nq! zcEo;r&(qaQ10-Li=1^@}5WYtnE~C9oC|nhMqgyeglb8f7l=fmizS_Fr16q7yD?Ix~ z#}B0Q_tq#-4&N&9A=Gs+vm85ZwloU$*1&iW3c6w{0&&E3jj6&izqZdO!z>EQ?ZE43 za#RgTOk-*~jXT(I84i~*b>G&1N!2R0LcP+0A7a1VD+O`YP*{!F_a7W~H- zal0*jK^t~ek1s+9nPRwO0cc6N@B$h`C~lu~=3J?#EAL`FSa#(wrHwXT^hpc4`{_sC zuqYElstIL(=m(iCxPlOPORCbp{{_GqW)veJ$0 zElS&dSlrGZwuvn{s+()+0q4Z4VJo{RiA(+~O$<1yXG|;mxh6r~;rCtPtOAo53BIH|bLB|G!ryN>4?EfgA@BBT_!f3#ZCJv(?a zIA7zv)EGHb<*xvX%Arz_>pLj)fB~fPBB=T)VvXkp{`qZ-lW*sJE)MH>vwETEp0A+o zw_Ss}AFx<>PMLm_)B^3D{#mG$vV5+9M00xA{1SEVSX$Q&1$c@da3LDwR@2<~pEev-QU}H;q35fgs|OFoI-I7?s7FDW)T6mL z@VOjuuWt}k9Jq?Hmj=dyYmlpGE(a<6))2DvU!b=2(2f$8qtCu@tdp+NW7ib>| zP;bebKDzgV>jCH3xQ`5&jcFn~>6BZgepuuTj4nI?Hz~d$X&|I@Z^fCch0I2$4In8v zj_iOV6j`q0;_8SzY^q+Jw1#0Ttj{%VtwDl;_}*Yxw2QFEK$M?kXqhEi=-RKcV1o0wLOww^C=jduKEL1XXa~XG#IS5#XWEXBhHhN@hL7?C zZ&^87ZSw6>TVMX(&a1Z9dawYnOV$j zZ|mvp+TnXL&nGnJ57BUS9l!yakYMdP1SEao62?rKd3vTx;wu%cFpXu!MG0)fTiV*k z<3Lc930l^B(o*{@R!p_+ne>7Se@~6Wb2Mh#GS$-4I6#!p4^!Z5si8R}-Ja=#3|k!W zqB&vFd43##-8(x7Ml2C47((`yPFo;qOlM1@{Ve0cQoLbTE3%0{f1c*$DbDT~h z8m#5f91T+MGO1GKh?j|dCLPzF0;+QNRWb=UL#b*%es-$({(UAr91}wJpB4ulUx`m8 z-~n_L7c@xSF!bp*tO-r7KxJ7m<`ZVnm8XKQB`oPG#dV$z37F54ygnG-tbDR`&{|A zY4nri4_kWN{ma%{IjZS!(>AFgd497hTJ#B#FuTCrZ2T?5Q_Ah8AlxCDhw65&fb?+kD(`OJ;>X?d?P~hAoLrWg3Sl4t#-d z(ZZ~YD8z;)-i&3N8y~_;eGXYWMEK(tC+?DuY{M)Xv*4tejIMTis(n5ZorNL*m_ zEJ&O)Bh`KNsq6F(k{&r~#@0=0{m~``Hg`M3X+n0OojS5&24sis%7tZ_GPqC!h%)V; z*?6&DLMVR{FS<=$bIZLs`1*kJW^k4csV`4A-+~;v(^Xvokx(IrrgW;6+c~wr@OHuH zO0D)T1zQE>S3Q20K&X@`5$xKR<@5oqb$FJWbQ(?;dqo}=1KRx(<{M#Miw~!T%*kAk zgfWY8p*(N4oSY>i$HZ%5j^m)FYOqf5O9_e~^(x#hhadgHv*5LUHSC(ZaIDqkSiq1N3NOAXI#UWU6hd=$_+n3Aj+uNO+-I*KPodui?BtkvqUGHz#K^wvpW)l(Dg@F`S z1@4zrkt(0{(rgRdx#*?^g40%#46s};b?;mpY8o^;mRt%;f7bOaMi=}YhlEbB6xT6I z_lAu?RXCyfG+`~IWl>CSpTDF?cDr=_*drmEBRcjZKHwg@{WzrUclq1VN;Vfo9}(lq ze<=M{pLEG_`)iN%lhuX<+1w(k4=H1ddJ}@%p31&$pC9-&=z?7^d{z;x`i)G*DxH*=)!SNPPl33oT@05<9YAN?l0&~m$?6a)H)c{S8rGQjYFEeQZ))xCvKAw z^)rLS&gf1JNndz}Wik`wozZ`*J#?!&B;L(qKCr$}S~sf(Y5N^9^6kEynbz}+#Pj6O z@K-_Ndq<*lN!y5NltMl@g2Ug1RWqEKDc%F4XlH~kgCIui#E zTiNYbm0Tn6>7lcYQCXmj6nIKs>U4#H6T~}8ITpwqr&QJ?WpwOFYeG5(_$M5kzyG=w z8Pe|Zy-gOcJk}*DY%ZlnzNRpA933|%iD@kp-k}NhV7uZ;Ia&ES)s5wc;XdhCD$b%B zRc-rxDxxK_RVXtVfNZYe7~Xi^asPtSRI|Pep2}Aa^{kn*4>6JKLGE1v+E}&){2WhT zX}EFvod#ZVZ}hY?>JD__xD;znJ=@@vG2v72?ngw|6f$&8N()m4jQnP-3XXoDVGFy z`4?V1Mq@v=duARRpK+6%=1_!yONS|LRFbz(bG!_;(HUyg&E%@KJ`}>&XPT`%*_`!t zU|I(T^XoBVu-5LtB)3(bafX29mwOpil1ASuN>sl#$xU6{f=a3f0x560XEmcvUi3gK zVu+(SH{OEBTd7A$xY!~3?ZDc92mx|L{Xj0I{}vlc1mO^K6T>bT_AWAH9n+X{HTV&4 z=zrq_9h}zwL-DL;881?wNUH1>uflhNB&iSaIJG2gq|HBJF((O{2YVx!SHq``>lS@f zc~p3<6cIZ6PkRll7w*4OGcO<&cH1iyTI)vI5cbdgls-@fE=US)jqfA=-`S0K1( z5~)%4!z0A7sX>;5*R0`>vX1*3fU(okhQx~xUSzlRRCD80CjEx)$g<7U2%oq?>BwBY z3ZWi#yjlwhq64O^A`4$fAh@bhX9W#o%?crU(=NaRpx#-$OgK-9*>k)soK&bLm z5qV7jOwsI_VV+BayD|Tx%TXNjOzxx#tF`H}TPhy2eR@3Mj86?~N$WD*YS?y%V;VpX zqlY#lLEq?oms%{aSqd{*3R6)b75HFqxs39AOR!Ilq`(}z3mFnk)lLB!u3j6q z+~{PyYYt~SVw{#ng5uqrpW6+zk#rXct$EAeQw87HEy4L}(_B7V=us#%dewR=F1)fF z8ZBfxb$VzX#RvZ)=Tq$c1fDrBtq-8Y$v;?J7hns&gS;L`)c?t?La@uotSiLeMm!z9 zdz`$q%H`~4`6!6DNY&v{9Z4&XZ!hHsAG!P$O%0cq^ zoDyUSnaRQhKb-0!2NQw^o`fo+@up!WjY{2bP01be30T9EN6O2bED?dXtI;uO=iOg= ziLiw6A%+%W?_GwHtz1WADX`oZvaa8CnH#>fKJPYX+!k{Tj0q7AV9FwDD41yf35=iG zrt-m*8l`@qkh(u5>`8dN^?iJPzl3ysy-1Qqx=SKvi)dklee(SGWr}{{xgZ!FdPH@i znRgEieB6w7?0KBlm*epna9#`i$h5Ou?H)|M(vVQA7e2ua{nn#ba;h1_X=Tpd?4C4@ zlo4&eW}lz;i?_^g(X9SK(zmu{A5u;%-czBd4JM^3N?Dp#)Rh)mQ5}{BUumIJY8V$9$4w z*4Iyr1HOy{9SC&No<>{ybvjWR1^8>^OlidM<?f=rqih&sHFnJtlrH{Xkb6Tfxtuh zB5O*9dsfb^sn$Dxo^MR3VZkhhU~mJ0BZn`cm?MV}(e25*rj0RPxD&^8p=sr6sjuMm%k0QZ}KiDl6UvOwYCG(jm&vR*oeS;EDqwP^GBM- zMNJMeGTW;uiRK4EuAjs5i)44t-q4&fB0?}R!0s#HYTk8-{#^lVVF4V5Z}jO%zHvkD z3LJc+zwkNAuuR( zs)g9MqmQ>tyVcZD8|P&B2L`M%ES}V!G02`#%aP)PuOe$B;Ye-V4t!EM8m1~dL#0G{ zpr3NjPfE8(_-{#lD|KR0tZeC^{bzgnXyzTEey-vZd@?IWvRT!nh^OAvh2sm=fnC*- zvVFjZgI2ioj?<0?d9rN^SrEGtkLZ3ojYXZg8nsQI*4BSEF4}iAwW-zvQ><$Ua%lXo zhV4o5lH=H%+-K~pgdB5`njrjsJbz$gvYqgBgIRa<;MhdKV0ez-?ATMTyjk$ii$u4e zgP-Yv(%z=-2FXF8Q$T?VLuCv!H?*mIu{1a>t9Hajm1|1d*q#~7F;0C&KqsqH*sclg zvdHQnN54|kS?QVoU^{oiwz~cFQz&#lhhT7Zyo_L9_@5DG5H8SGQ)gmL<3hibrNpdRce+E@aEtP0>j0HK7KcPTSW61?N53m6FF7B z7{O;4F=>!azWN4^ncyp&>OPDs_Pl!@?yY>^ewJA19pfQ_>B>Zh5xO4|WSlj`BmBS{ z9wiD9q;BU}Ak2=A-S}9)%yW^nB)v2P+wj^8T%p z?6~uQt9MWX4Ev3`xRZ`GG1VZSKb%>M1+|^nxe)~X{_>86yociYM5JXtEwr*nM+ER9pWOI%N|RX8Mf1 z;J730k#O)0&ISbfICj$eg_FNvx~Lhsw~dIYhL`3(g$~5$=j`mGcbs?*!u=j!B3zck zemBYFP$M@P#(Y-mHta-QM2lk0L9#~vK_b>DnIHRMM8!=sl#2?@hT;EZzqb|2Pv&y4 zC%`bv*fP>1R{8YKKsg`~T*~oh{2+8<{J<1luO=r=O^~g?Lr>x)Wng|{Iw80dY`bRH zrk-o5vSIMYdvD`s%UZ;ZP)UyCSDA5%{p?M+Q_!}QU`~alEJuq%i%iHvkKc4(1yLBK zhRyfbF4WXg;=kCOYKq?zT4g!G`5u@-&|e&@Dt|wDV7mOCU_{(CEk%#ZIs3E36l}UL zh-DgXwOj6jmv^2gRJlAUWqzt1^|F|^lkvFaeDI$C>P42{EtpK@`sd~+A~yGeahnS_2N8+kyMDo)*^_K!wC?Vrf(wp0#y3lwC^wwa5qiCH zD&Png%)Tv|+;&OKr{hZNPlgn3L=nvwUnliGnipoi!)u|>pt$=j>THK#D@Y8Z#*!gQ zj?eZ8nVvfRVa`Ce6t<&k*n*V6V?!~QacwaQ70)40{0dJ0<$QzN*vN4|?F+W1^v|op zsA8K={P3!hmA>qUZzm~|y=HhvfpVXlK(#^SSGK?ZGdB-U`>5J4a_y_iy3cQ;X~ACv z&#iBZBl0X=zhtjGFRxc*#mJ0I1@4|hP!H&WEBA;UMv2IAXrEy+-JILYCH~QiQ{be|X1CN_%Hg}@Xu#eOOZ>@a% zwQhj)ctYQrvG;J51oE$!o0rSZnxeicTIrTS|8C5A3qs4q}+93%qLZ1$7)Z@p1sNv5JZJ5)mEG zrdpxroQ>8q0E7Q*NfGWb!+klTM$<<FxU74tbL82#T;9u{(Ru z;97oUJC|AtlTSDUCYVfdES4C)=rr2_gas{6$SM=XS+_f|hml>${ zo90`lc(PWC+^Yg@LIt;?vCoU+J4Ied3UyPo$m|0RKQ@va&uEs`=_&aC-mr)1i3U(w zKYKF{L{soPF&TdIy%whAgYtZ@!XI_Acd}p$_05ZhgLwn^KbVgaTLMdAscKuh6m&Jg zxV3T$CiN~F$;~dcKY6Rde6_t3?g$vqE9Gy`1LYMv37a=D;Go#mN}_S$>k%i#?%x6# z>^;{*>bp)75z|kqUlEoU#Fy94hXO~N*Msfy=-3937ze=rAX&erlD;@vc~pyv{dX(! zmujFvUh$kdWy=)aB|fEh!j|9n!>mrbtbDsXpPirPkNt)8aw<{dYEc|QE`I>Nr_n82 zJ12;v*90kBr2ifqpnw<87?+Gl{}^v@E7U)awK8ElxvHvtTS6-Mh7p+ulIMy1dY;{ZMCZYk$iGlVH%z zuj6$}g=`1!?Vt1_KZE;4p9^s<#Uiux*1tGKW_cyFjs*7u{Vz$a1L7$%#f7;4-%UeTxOjHS}| zP0E)Icnd{e2=XY88>Mbda=zWWf50>+3y*F4xdWz=N3^W|4R!R`GStj5p&XsxU#cJB z7{GF_(N9wrK3nzrn7oDX1H`K#C)jhMVsQ) z4u>WE%bQ1g?&B8SKtC=t+X6eje?u%gTu`k9`O&SQGp&t3ekN+^$fBTWK?uMZbP9M$uYR$<1>g;a82je@Jb`&81Au~7vioc_EMZp2vCKY~D z>^1jq_BgQ=>b_FSpA)B;bpL~X%NdIko8w6O0#_7)-hbrcH^OS{1$K1L9cAo*COPT_*H2MV3LE=X-asz?))S9=;`8jFFo}O_)0gh8A z4ip7WOoc?>w+?Vx1^&bG01HJ?8B})r7=v*||NltgE3n&rEDK)`162K6j)1C5s?hW= zTjKJvpLiuA;R%)COElm;eeR#>SLGee?a5ZYHqlF`mu5H#hlU!4-mNM;G$YUXT=@2c z`1UlzTl?IhUVxAuAs-Jp?;UmYh36*t()hX!n%SJq(ZrQMGOTFnta$t%7G9Qh;o}|X zBiqiSu-#cel^2;8xF>pL9eYm21@QEr?IX&C-)eg6{2ju`P2r%%Bi&iSPpJQSe#8FO z#k&>_p^=-(b%=^evS7YlfKFr&ay=sKn6EVo+o-#Hn(wcd*_q|DM>5*;nuL)j+4zOn z`_v7Epd)7_Nvxm@x(?QUPy8K0%nSRzhoGruy=3+)?4__Z3aA?9$`nO&0ivBGg79sLz3)FYx=Q(AfGie?sJ3-f(0?_q-O@j| z5Qv~*A|cwM6p7-;_fFT?Q^5^vi!>JO#2qBN?A4d8Hmr z&PwqfO*o;_>gdCFZVXXXoaiq(mInnljr#xW*tt3%&O@EQt5hyiiv>^KSrB3>hEkoPduQ`VD@!*_Bf2Iv-*v&B=n6rbfZP|MVl~3 zt1w6FuyA-ex~W0AvpUAh+^cw{nd3xsLX^$97F^Pz9;&(|r3mrL_pD z`?ly5y67Cb=q!4NZCJHCSNGHa-w`112w(&Q$Q}Y@j{qA70HZ^I(GlQvp*U{3*yjMC z?_SQ(Sd3J=*Fy*b;O*_XTyfu)l<}cWV&6Digheh&GC&^NOPLs-j1+cxE{Q zV1N#HrqTLHG`DgMxAJ`U-Iikj!YKQ^SURjw@!CB2TgvNt27! zjH{)MeO)PiWUg5608k18JnL|MJhW(>@vV2`!kk0bw+TbG4twV5qgjhtX!nX^&#jK8 ztcu=;ZIvEwl{VaH9^8a*_cdo4laBwz9{-VJ;T2|~Q?=q+#=b62kh_TL>IIT%i-?pnPp%>cGX{`A zqVwRTN|E3I;OAC;xpXB2Oovq}62RZhtsEBw^DSvVYJ#aO`bN2JKiY>`@!A4(KK`HG-1h1jY8H=ZeGk29)(*Ox1TAd0aw#WFBJ`lrNF ztHe?6JVl;7`0prB9$?%Of13+`pJ0o2Ck>y@a z(mq$+K1Zwt^8mqkK{h4q?&0hNCKw%x>FpZnnZ=50)rzs^?rWur;|moXvP3=FL~ta< z&koei4ZIddZ`3LZ=PL>m8*O480bJe7!`;edx6*x-(lbjHg)0<=DK?-&<%;8r#ko_( zxlv~`LWPQK@P7-%x%0&%?&arpX6yN~dnV)i{MGBBnVU4@PLUR0#NltwXIjqw62$(} z$^HUi?^Ytp8#1e%9WYuLFq#=iKiXn(JFi?8IVAx37PB`gr27EV+hx)-ixj&`6szGB zegLp_FGpRN<8v=xbT8*~D{pZtw_R@UG)(u=PuD9|?4k^yvY+dx)Tv_-d%f*>G8tI! zn4#v~r%<^126y@O=<)VdW=rjgjd1`>XRmH{zAgyz!Pb;x*K#SXK@4z~lK=F_=;Ppd86?$*643eW(keQvFN?)VP4 zS{iva-|x!pEGYBz?PIqQ-Ybj9ep}|9xbjoNM~BG>%ISVA+b!>NdOsyR(TYgQ_{n<| zWg#OxQIfpNH_A^bAN>VH_APnNxiYfa%Z@ML*J>x!d+R&vtxrg?tt1HK+y_lko6V(6 zErkc{;M)j%Hdy^+fIU*hrcy54Qt*wdFQdRoo>6XoXHYYlEfbr%x3||R@xj$|KC|(Z z(<~!Mx8^@d4~@&?JbdQTyo;d<7vRE3M2cdQ&Y!lM$%JPOinRBJInw5!7t}60wdd=A z)Aez|Kb*@5By_;txTvecMiQLYAzT_}a~qUkz0a^DZWu1A@E8^JN;(u{hKK<9s9R1R zBwByL$I_J<6e@9@L92a>sn^Y1=^@q!{lx^HDp#|}8_HMHI!Wlwr9`dZ4rgAJ21=?F z)36eRDAPVG&`OT<;$lv=t2ftfq#j0x#Z*3iroWavA6;ta;M(CZg>{c9_zAOSuADKS zPoiXg?fl57{kFk^kv;jX8{G8-{rKTa-Dz~yT+=2ASKthbws{r)#p7V9?ecT?!Bv7|! zX1IUnB6%Tz!ce`X*d3O6ZB(^k(9+}%Ye!114R{Df>YkhC3950OWa>X1nbWo&)2bQK zUv@=x?uLoJ)py)w9q-geGfwnJ4{ZIT=?FJQK3&4Y^_G#z)YMnQv-^pu;sy-><(SJTp%D8H9;a9$7J)b=G1taYaVxFogi!=7P04?=n za@As*05LNm{lwIIfMy0xi->g;cTkN$RJ;CpgFeH4B_7Wst&OLHZQTt6M2fIumX>G79~l*ihUf zQ&8|U2~i`N+dC=_jJ2mLUDgmJ^6oKj-wbzw(LD>u13f>QQlsw3z)d4FL`H;Ux^0R} zi~`F#5)A%L;men-`kEOG6|8lSH1&B0Jmai7E2}jfQa{KYu$0dwD>OBVzi7^}6?6OH z1KCTM@%|F?1L%RgB@p&JtCekTe%=GYj}Yy0CMNE*AO&HPUpnW-2eXp+)~J){^FtFg z*n~Fe0lvSJKE~}_rybnU$3k9T#EFS{=(^WJ!}_URm*m*C>SfH+ofFDC~tr7{=G{j2iE zzR2t|v;e&pwn_^p!@JVNjf!w<#0?v~*X zpTZxU9`A4E0@K z{#OGsE@vyfNM`t{xM2j8>UaJ;5hh;!3&yWq%r|HH*>&boJu*?Mr3D(z9FgwFZqMSs zth*glPiC;s8?-s57YwHNK1;W2Ce5>_;G+WQZ5j4ZM?TwI9_R>v=AsKd8!8+3IvIE8gKDg_id?Wd&U+Pf~NhS$ay)(qS>?ve* z{-HhkUBibw4~u9Ozvv`eJ>?&^w*JVuWQ6FJ8LZ2qN8?wAHva9M>Mir(KAM>Mq>RO; z7VWPtr9LdG&B1X-<#Q5$FqDs{J-zx4X5^M6U_Orv{>I3INdzRYuN!_@ z_U*RfoWFopDr%nH;&xH$Z@cMg#T0(8uwlSNtySM)={G3P(ZM$~M%;A2Zo(aj4@DB( zI~2FdwJG+7b@rde3CQA;9-PA88`aW6)+NZT^8secBY0r{a78!k-QE(f;aLbon zKgMwT!|yWUyh~K9t&e+Ix*=vcmdtQJSfm}WAM^D>j0Q1^W}k3{#Y-&X4Bzq<7!`-AT6kHtR?Lwknbfb-ty}K%hVZ+Lf;RJI4-x$<``om6 zLK*ckF;sGO%+UPd=sV9^+Cdr3xzRasA-!sK=F(H?4CSS+$5I#t=0Y;C`}ui%k+#u zns{8ePNY=ZIlTu7WHHCGlCna0)>$#6LJDD1a4#te{Jk&MiO4zu*H? z3geUd#T`v3lZnonE)kOoo{aTg)D=I=N|o}O+O@L}5BbR#Nq415p2K-RR#T&smK>Fy z&HkHA>L|^S#keA%x?K_5AbI2m`)P}~nh9;8OM1n=sl>`$8WJLlrPHsipSTpbb#au@ ziAtoILu4MA4gEl!zw^i)cHqhznPjre6RYPK{V+Cltv5WANrnwzAm&XQDehjiLPlAp zPOaC9WE0+OuX~UUq+Zk(HW=wlX~>pfh`OvEP$fpwC8)S>@ZgMh@=J@FkEZ#nN!f{E zY2}JX!+QGe3QN|SXHXTGPE@(Q;ohgbbXe>>gw_7JLHd_?`_gwg>ey3uoC|!e>m;}w ze_9Xr^OGF3U=QcLB+`-JpIRu5rS5=Qu>RX7x94q)##F~6HcKz%V}_R81%i%$P^=tCz(AhcNhXBK#= z=zghM>dg&0c|Vk|q`lr+&vd)OJCIgB*K~a%EfSkh+L2HiK4&koJR88|T~ay0g3Zhn zGT|9(uT;tcFdsubE<=?T2AwLE=)7~4MjnqANV`mZA(R7-{}3MeaNXivA-FFK_6Sr3 zCvqL<40Ku30$mA&udYCc^0r%cNAxPPy#Ew8$U#0l+1*#?nj_l-x$|iS%k!%siLEC zMR;16%Io)szdsS7MU*?{Ow_PGIh?=VuG#Q-EYrB78D*tMr z6iN027!9=~ZAy22NasY|7qm1t&Z(t)WaX+UYcv3hD5KeFAH%eV$KauoP$%S>B55#3 z>TEc-?AN(|{~(xU`dj5Eg<#+TyKbubIFy$wS3${gw%i(m-6g@tvqF3ucPe{!wdw=) zTEI+-YqM)N=UZF;!*XFidn+)Z0V4f%Ai&`dB7P?D0;^E6N8D`oE{PaGe&5f0UyrH` z8uh=d3aY6M;NHKAvgBCzw7AvQV+g80ggIyV4)N+2_|q8$8I^eL0We> zlV7_gWO~u)L0;uWb*~QD++P}_N~e6#?Twz_WOJ4rIp00NZ z5ZA}|7G;4dk&}g1n@r-;87ne@0no|b;eu_nx&l1OA!;ntS_b;e0Wlmec&a!)hKI$L zHG&RX|33^A3>i}#eszC7*SmxR*L9yZM7uNk^aU!Eebp1~%5cIzXDg32gU9{03ETYm z;kn?`bzwrL&FI)i4G9$ox^hx&XgCD*fWiAipc~-<*xROsNOH zypA>NleASC?sM?mTV`AuH_HV>1kt zM%qc-%w+Oj#5^q9*1id})El#&d!G2UzItF`mn;0iU|JqpG=)t_`mmW8npCh(H<8A$ zSe{~#WGJ0(ubDZXXQMJD(jBC--GnR!b}LZYT%V08AT%F1Fo#t4oeI3E?0iHO?4jD2 z;K)Dmx2T4mTFyfs|3cK7EhGhHtUF4Niy~&-8AvQPW6N}rJOrzbn?E6C6>S#ObpxJ` zFw7YjnDSoNS_#+L6Mb`>@d1_1qBB3umX~RDD;Dc_y5nZvA%(iwp6JMmN9gSALir8b zI3G&BfjkS~8~M(rcvZ1X$v>8y4o7oZu7Mk~4C4>gse%&$IA=rXx@7ICbAov!z~0*NGN)POX!9vuR}K7F0~b?1f$P0CrCkLN>2cK_;|KZqyO6uG^Dg(@N!l zY2j0RZ6~m<$wuGQ9(TG2azSR*9)WyF5Pu-ZEBJ5ay1^^|Js&G(*YlM^3l zo6l!2&n(+q*C%TlD(1;}rna-6Jya{BRI^vaS49REJRy0f-@P8dDWWBmMqPrDaLs|W(r{=MK<;x7yQz-29CIh!P5 z!pxpU9wp^xY`zGKF|6(?ET)cvq2s4*oX>mZ2&W5D$9=0gOotkIXmWxYQYtQ! zJiUYb+@I5$lcYCYHn)ee;(dmiIYrl2?U#=V)wi;{`gQ!Bn~U)d&cQ3j6WjEo>-D^5nuA(7p6rQhFwZRFL`#=Uw$G3 za=)J+JpC=Qdsala441zNy+;)pSDRf{s3o7Ff>BQe)2d?yVp`LNx>dOEM-Ba9pQluP zr1RSwvCdO^vOYkAWg+>Hj4 zBizaj?UM8hzd6MW>JR5#naiSstxQDyVF6dMaRejc8TCP@ZfgBvKOp#HOBUEY`2ut$ zAr9lUfm>$lKuGw@a{$+bY>X{LL~hE!exgzPJqRES}`qRpN z2icqnO5R=2Q!yR)cjn4s!AFR-^i7;RYfw=p^xV&<8<>l8S_2?5C99;-g`v)qB)R<@ zieO%G4Q4Dz!DlOIO28SOY)n>DW=l{X$2|&}>kj}HjC+JYld zZWBiuwPY1MvDrW!J+<_YpemUSBdOTMQ4sMH6Na*!GgR(md34Bs9dXkm|AE^-W9r+% zzo^0XS#}K8ON8Ze#g6#;D#i7$uu_!mGXEpn+)L!!4~L?FXZq2Sp}4N=(io`HCgyaz zAxIo^0UCkoIN#HtxM!Gkl0~x8-SrIeLZNebmhNrCMGRwKTi|<;^f5}&1H88WD0MKE zQNFpS5Q@p{q;`+t$Q7?VyX&w7hTK#{AqY0%EtxR5ygAxws>Y3aK46oOX4Dq^)CZ)P zph`V_vJtm}2TE<2wX@$RC}w^99e_k%1K&zq3|HYW8+~w%GPkC)ARc9&-BEbe%(_el zG9z>_R!4q<5Sb}@bp@27`2`>uM_|7M%90PC(FoFEaZE1P_xc<+c1H9v4fG%8V#YVM1b;ENVWY%ZDZttH$88IBMrdlQ*?E*RtR;D0|{jz8K$f zX(m}C1nuXC=Ksc`IqyPE9;zbmJ>+?7bf7-X!7|mrCjSH@YCvv4ja_y5Ko&6n0uUJ zr>V{uzxrR-naf+IBu-bMD_6+7|XAnqIRjYrjU7JuZ^Y5>n{7$1Zz5X8)$17`FduuQoMF#DqskU z5ind(CoWRB(4~Jjtx$gg#%wd`e%EK)Jf5h8#PpFn>gr6PMu>6ItS3p)?r+5glcSEe zax>7-uBLV3C*Ztrskk$;-e7sJtO1f9C&@BPgmJFzC*+?y%s0a!_?a%6{p&RC-$b#Z zfT?VPIHvp$t3b<>YMbJrMmdq;>`LK322Uwsz-RvZnohyos;kg zV&YT&oMhtkcgdRt;_mSb@mst0QVxgDpR>uHy8{THd%bywe=| zmPg5Y8bs+>yeL|2%g7pjuJ7G<^(j{a`#hL=cw{8qH0EYrI;N$XHm&Mj%Fj;rdd<%| z;HFINm0X}LJbVtu5LsMwbn(~6A6(^sSL0QF&2h%mB;r>mz(vW_zT?(AAaGe>ig>i89CR^DCW?W^>YC_K*I zk>1DP(vMrz@Zi7=zLRtMmj{8NVqKr@!Nh;v$aT7HZbs5v4DJg%PfH(e*H;hoQ(#+U zPGFO^_q zn?dIe zomnyPR|qc_Yd?=VZv3g%6M32b&hW`1fi24xp6I8Jp2B7s+4<^yn3Gxk3=uHY3RuPT zKDKBhPrb*8*}@`2I3%r-H99L@&u*YHp}E|()lqF&V!FthS={%Bjxe)CuuRPlEDuD7Z=wYuhUFmeD^e9#25ka6n z2uTdJ8NIW9nA4x53^_@rqd&X1iBGcs@;Rw+Y!dtn7C#vOzI3s|_d%G0^@cTWnPRt{ zufs&bvg|9MB$J*KWMLGt;Q{8RFB`i-JOqAAS_*CuA0b)fYqx6)VTWOr(sovI6~=Ri zXv@y+0gY_@PE@^-)YIlyfvqaD~2GvhF2KofYtV%o#K#V%IoSk}s#~r^}=r<4@`c#Js2d%B* zBD(;eT$qKybX{Hq?(7NLY4r!{L_CRx>!iVUs_6Z9`V8rnibaq0RP8ZQ_TgPCV=*FF z>iDDxKhj^95;{Pd$B#Q`I7AC^*38G3?@x|do#UQ4QaYBS)FkWnpaBl7`ofG5mlLMN zHo5PiCeWdf)EPym?{&T@-*aw~Id9B)A3uKF*pO<_-9BkiIL`VYfUH&hUPFs5Vb5-$ zS+Fso$ia_Tz2ua!UYqEu+JFd;hxm72DbB*sxShC^^vo(>zQw}{g9oaHNzI1@XUyLQ zv_&~aJi=C3m>K1PpyN&g)Chd(uqlfI9oFp*z60vQIQ5;~0N)GaPW;VdN3eQYoNJk8 zHQ{d>f$!?t3}&w-%bXeLk8PFtequoB9JLoMJM(5qbrG37O^{-(HhTRLMOE|OJHttZ zuw(lfhmNfEIcyac_znQ0_71~K+}CEl4dKSJS=m#S9k-wJQE@h>?(tv)jSaJwPWgnx zDEpOiVP?l-x)1dYj>Tq&D!AjNCrvS6AEA~nO7`qNhv87KQ7Br88-`ZQ!>>7n--zFW z51P?8qXf{rhR}+Jy`_IYFP678PcLiO)%%%TvV|%6MTk47@zW1IEztS-^n5E~CzweO zomZy&wGC>jthhi+)6iP|y7BwxVQ1Ey);6dVp%Rjn2>@$o5O$Q4u!h&xTR=ecH-b*v zAxyTLb|+SZFN9}pWuXn z5jkY;pW$jl=%eMFiVf`8?tlr2fxDXWgBrPes&6MFv7s@GB(QR6K}7+j2iLHa_fzuB zF*E*P^~x#!01fl2VjZkYKQ3YOPUQQK#Zd8QwZN_b;3TLm%m!>;yLdcch}D8+VYj%3 z$)lv(qyjw>u&%vbZ@E!#P|9nd9Gyx08}V7kyAJb*ll#NqlE1M3d`0VUDy~J|m&8b0 z6$*%74ENgnSkP2NYkQLI%FOpHm)W)&xGGVJqYh%*ZHF|mc`)~-L7h|Y>+)e&h`(?K zLNHwj&ZQYu=7a!}`z?vX(l@D0Gm9fNweFzq6J&Gmbohy`X7^9|TvkA8>28XS&!%lG z$=8dZj4?UmW*2kJm)8~+yvsYe4F8<(VVrFV`dgw-TP`;2h?52>hpy{*r0P9p%&u9X z6e=$T1=I(pYc_3=JavZO6#dM+RAatl1hxklK)#r541Qe!l83YhB?507;Ip&OJQGA` z$C=;ktbm%7pPnnbi(ybM=O^@G*|;R+sjB^}EH^z}*UfJv015oC8&!Q16%6~$+2g!p z@kk-T>V(hw0%I^D9;JJDmt-<_i-)O~YfGuF9tTJSU|Zw7%%kfD zo9HNm^p)jdPL8`1_fb-H-K)@4sYXue#8D_E}KO;A*ImMpyorG;bXxn zk~$7nr6LbcI%Fnd(k%D;25rF?I~64}z|#kppp=XNY80!pxD&RDz>&Jxk8I2GZ%@UZ zGp+L1&s|ktqy1}g;;HDrho5iqLrFKp~l}z9M-Rzh$ zK7u2i8)f;QS?bDxEO?zhb zOP<^Y!xkJ@39jqwtIE>p)34++I1RP5swfH9^^C-!q73v;fov&TL=Xm{ly~a8A%^~F z0oWx5Zy5rZo;>dQ9{65UQgp#hJUIKD5iwTnoufn}N!Azi)C#>5c->RZhx1qENoDlW z!s>A~bS0L+*LEk8tELLv3`0%v$%SaP32z6K4mj^Rk*x(QR39<{yMI&F8BCQ2TV-nqO{6HN(^ z7^w&m9}rIbFKqQyTibK->S~NGXoKEJ8R!mlf4=U4)yis5^FJ|m`d#1W=8n~Vj(0m1 zkd5iV_m1LD)gCagbS)mjt5gchsX#4Gc8_-_(b1D1VP)ru6Nww*RNElSybK<)jv%zJ z{jUQWlbqTHz0b9?G{fMF_tV(rw_KqJ2%AR9{j)z^jealYBluo{S|!E2n+O`eVmXIX z+e{lW@bg4QpJQ(XTQ!kdr4T#56x%YPSn7WOLO{L0RXNea^ah6=#EDxG$WkdTV-=>_ z)v3GC!;v^Ttm0^VFfY5~+^CMuk->Z(LQ;K4_L_6ZpsNyI<>VeCC1~dXJsGycE6Px6 z_T(mTb)m#PxuAUlf3>|^n;o3V+YT=O8u#0&yGGQ>@*;L}N&d=&U^|u9oCKBIfG4&E z@9(kw%~Jms>F^3i_SBuP^G#`Z&v#UJ6NF(urQIK3-d*24a8*WM3*jUPv^AN%(jxrU z1M}`azq2E7f@QwY#b|4OE}mKQ`$1dt!*wa_N%3Pa-)j62%s-DkICbZU#6~sM_bxF#YL)|o&~OXRH&KC{=`FRz-4pEMt^%4qd(5`V^r$qNRv)TZ>9)PK31rNO!+gVmBW52# z#vpZF8J_-iT{cTgdlLxDZWx)Kl}r3RqC^o&0O-B7Z!f>g>w?!5`OB={t-Gvmdt>d^U zxJ2A+Y1_Mb%{lC=S1h%(?Y4DzipO;X_9^EC5^mu4q6)#@mx$NyHIOl|yWO3Wr@4%qfuf_T!GrT*t4 zk>4~+n=gTD0d$XPX{qZP-C|jbr|UI0!;A9P_E;M5{%G3;%i041F*xmklfeoeYRU(t zR%knFX~Y}Kf(v-;pZ18i6g9*1ytPMs{-7&057O7-E%)%u9`y3-YRC}3(00Z%G`NJv z^>RF$XUyg%anxpVyNI>`TBD7!VK`=03|?{?4Bcm07{K?*OXmx5q!fAqD}^F?p`i9A zAH1oCDs`9gCLg@1hIx}O{7vMDFhp@D7=1l}n4%U-!Vla%zUV%(Dt!s2*nPv0D<8Ys!;Or^aZhmxa%zYw1=5F4PQ= zrL*yJE z&H11_W;w4C`#DN%y!w+gC3zYi@`yCUO)Iveo?Oy_=BMTcMVoS@qfeVHKx@HHEuTWV zMJXJyUYy>mfegM>ES(j03I@T0i^W5}e26y`%>s0$0#H*rs}cE^t6@b>N@7P+~hd`E)=064Xv+1oBf|^>5C@ zUH#+L`7q?6VD1bdKy5pAgB^ZQZ3nI#Z#%qdIxg7dfY~knF{B zky;m`WOf#!k$*V!hKdf&xC`G_sCynb9q5z;otn-G><^ri1Lri&)7Ll0Or&beX1E|; zMbwzKD++ffDBG#oMYg=C=l`&|@Hoai#IfyN`N)t$VzCx#bdVB3T*<@gyLih(Nw``=`$gX{KKOE*+%bCVyG)jcj+uzoN)4O{x_L`m z%`j82=okBKogO?!J*z2s8qrk9piM=oqcz+Yk`wHSkdnn$7pqIDMIS8Z$!0-7Br2)V zSWjphwjVFs2BCyr!e!+**LC?e`yIITKj`Oe-+G4NccXj6w4}fpa(a4NX9KI*)Vh_0 zU!QKe9|y?6WjrMw3U^xIU4$a^0@UT%?7vs5s~@fFiqONCqk=y{1@6uM`C0{6&9e*J7+8?}|x4rARmM)5t_1|@JIJ@y^Q*uV*(ZC0ozq1XHho|O_(e?r1zD=@z zqL8o9p!OM9H|a>JicF(*ltS_Cc*pxi@mHfo}o_2%t~=KsarDD+1dZ-jhWD>SFke4xft1QG6hYHK}7l z&jh*xhh%Nlp;>zn=Cv@j_yQ-CoTrt5TJ{jDm&y(F(()3tMRCN#OmBhto!4x6CA%LQ zsrBas-vIg32|5OXhLo+b`Ahwsb=9Uy|M9ZvoXJbF;Kj(8#qvsffSRgcX1yw#qz#ce zaT6bTNm{gv8OSARcxp24S5H>Yz^B>CjB3w&%#zvKtLVLbAf|bs8@P^7%t;+Efao$+kSy9o8vd2!j8<}qMsk!(H0^oGw4{~W?Nc`56%Q|0eBmXl5*@{#5ch`T-b~5_%4hP8?saQU-)a@88=^%ybOmT`8S3wz1 zb-u$*^8X_#ZZ)}g6xfSPmNmWxv!=w(>Mbw!I7(;rzCek~>2UjI7cX{} zs71`Ni?}P?B@Q+7@YtC>4sV5Pxx>?^8s@r79c50pqco?&?e%&5B}kIq5OH2XvCCUL zDZ^gjs?MC8K^C1jK2DjO^5T+meW5sH2l$HpE?=Q@siVU0lR0JYj;F=~ZF5Kisbl|c zqJF9_ZsdEWmbsVTbN@|jc`Nk=J!Ai)f1xQ(nn+!!G3T`;QJfl0MtJzQMoClZIQ06y zM=T~&QeVp}FpL_0{{2`mByv*Jcl^|U$}JvWoc-CAiJ~|nT0T8}D`UKq%{PDvuV=#V zuz48em%@b8@ZLu77ov`=D>x7fzC`MjztR}+Wi#PA16<(W^Gx{9Ot_t)>vLBgUulj) zS0zv4*zR?)XrBbR{=aA+@6SrxaB%nh##p#FF|tDF){A$dJF;SZc`V$R;J>%HnMr*~ z>fQ#j8WmQhZX;eW%B%bhX2ONcdwTenTQ64|MpFOk?5fOIZRiWWMaI?Ho9>OquZn!X zcw}-r(0XvR_X#?kn--@{O5F`#FP<5%?njoC42V@Xud|beN2zPo>#zUk<)X@$~YXp?59md)UiC@8(YH~MK5LOY*MAIISuxwf2?h2rMAt;Ur&AJ za`CgiEvV~G^~hsH41YqyXo#M=D2KnJzDUysQ$LmZQ>dRy{X*)ynD?GC2qS);r{R~V z|0eYhGWpw?a2E}u1Xa@TpQEIorr|DwbT%w9^(pN?LH*NAekGjD@OBcVk%iWY(-+rJ z-Z1I<6)3*Cjy~+6@4~+&gf1J+o5YO+HmQQ9&tc*=s*ED?mVUdX-$+-D&gxmgaaXEb;b9_{TF)eozcQR3v2B@@&!*9K-VE+G z+K%%1S}O-5_|7KqCbUk0p5&+*5sEqWfePG3!~xyrb_; zAl_{6;s{1r%5g&&*2v`eA>l(Xg!ma8wt)K`g&3EvUyF}$DI|qtP~8q+dfN4cIrf|i zw|iWAj;q4!FvjIRK3pqfHM?*-3u+Qt4K8AR&`U(7(%uaa86k(`wKNvV0JeFSA>9A! zbVYz|E#vs#sgPJ59uFH=(TMQk&J+Ap;uwYZGjQKt0vh8;k8ISUn#2kC$b?Lc!VzKW|@(8X!O!>49CW9@xd|0UA;5SFHaXaQ_4DJqp(WydS$) zaIG=~t73_<{BP&O$1_+5ZiqxF{EF6nn#XyXUg6|7hLx|-_{SRo#&V9UW-wFDAf`Ip z>OmNAA-_L3yrc!I9tb~p4divEK3Y8$>5KeMm(S@=hmNYklm0LGYm6gi$cGNAng;Hd z;1<$q%i*UA+(SArOcacElk9hxRM2{q=d)=(G`GD>r(CRd^mg=y5ejUqGP0p%S$|xg z^Z>QLcy5l`tDA&rgZqUhO4Fpx(gLYPnkD_WG()->{D3q~Tr3GAvZOK6-H>nbIBBl1 z>U~KNv!n@<8Hzlh6I z?S>MLo6gu_fdAGT#u zuLK>vN@#vmBeQgAU3@>%72#RI$z4%1 zGrz|&$Ub8RQIcSUCc+5K963TOp`!|(RHv}ehaB)wI`B)mB1y!Y(tUNT>f)hwwL?_Y~U=b&Mo#CvQs@DuEJlhILrGAhdZo+}14 zK)+@k{X~m2T&hJX-q0hOkep`cWFTj#x8y`Z8qlycjD{@?4L#1~AYX)t+T#crdQ_94 zLE#JM*mEKUuY}fL3+_IV>RkN93*6(PvAPrt)Ft|#7+3>OQT2xl`Xh8|b7+E`wvY-g zTkdJ5oQvTseYp(=<+>qkrR5ZQUQ6q^nNADp>5bOMYHeS6#hGn<##I~I)4icIZ?a%}8c4?aMOl^f| zG7H~SRY)(9v<-=N$ox&!t=Iv4YknDx6v!kkfTyRcj*21}A9ucuR)~KA(&wR3 z6p|-&-OCeEx|gS-bT2La2655Nm#2GEDltk4S@N)}Lg~T}H@AY@K_%~x;Qtle+hXr$ ze;cAJ!-gpHnM<`a-K6nc$vgo@Go}$eK*wq54Re`TA3FX4AGa zX)o&1b}(rl>9Cj+eQ=PY4Gs+Ykaggi33UqeNw3n`YJ;RNahWV)Nbw|3ho$DPu!|XS zR+@mL(K{U=dHLXc=tI7V_sR*g@6#n@(u7$`LcWqPNt(6{{v_`(p!G++l}$2ffnAoP z)X2j7tw#7#uaAvK$kSFvIwBIc$4cD6>&CUX1q|6{9=IZKODPL2!~6We{W`%|uD;R9 z)h%?(PXf0|v~LnISU>={i^L1GmAlthlP4HEK*XLr*(745CoGGe&bQMXAcWaQywww3 zuIY<)V^UENMh)&~9+Qt!d=R)K3xra^?+A!TPUD>+0=KbwwGj$$k!?Hl=99^fjg|UrxXy4Jk$w9`@UAAkyIr4aC?t*K zkyY+Ox?>PG77|v0t{KC(@G>VsbSVUvk*FPWW3Y+jhA0%kFiJ`y#y9#liNB@okT^Mx z;fvbq6u6Yyj9NNB+$sSsnJ9C~ENMS>*4K9-zdVdT>G(4de_%v-hh)AARn6q@K~bav z=a_Af(uAo(&2;cKAU8p0@sK2}8Yh_2?jD^j)Q%H)2*8u+z-Re=;dcVKh3 zsw}5$g1*gNR>7(JEBcp!V$yb~5H`(b+44q}1!bO`Fqoz~PK0~&f04@{U zEXsm+g1-RV{gO^I8Eh*vVo~fJwsVF&^?TYwP%e3u4nPQ(0F^O(fm4}4Um6cO{R^jZ zBnI~>%Bx_LxQ{6fQ7n?EaU=#)d95xC-=HxZUI=&(AGK6!uDXgs z1Ob=mqKm5+{X`LV`FwkQqFzz=TV=hUUQkz*mFuc2t46Q){HjiOS3hQmtA7|c({<{c zs;=%ib?VfqQ#F-KNH({nJ1m^zn&&O3YN)LV$!0@M(s$%*p7w@)ZuNS()x2-93~*SE7r!4eulXT-A&0?J#HjT zO)O1b19nOyJwa2XK{2|S!z$vcPppmI!gIX@YtFT?Te$(9IOm*jE&s)>$ROSmGK=?w ztX^b?gM>UU3$44>0ror2yu@6R@~M=-I)$FzOX-2|OlUpfI!xtO{0VO3^p)=CRn#4R z^WD1x7Ox_01J~)8d!dokmA3T5yYgS_A+n}gy+71u=nu78f1o~4>!eS1(y}k01(5mI z3En1XXZaU2E$dJ4i+J?Z;^PeKP{_YtTZd}WzMv^A_Lxvj=jHBo5gr1l&gq__byyvV z#gAA$<&lwmA&WrEU`#8cyn&Qm-oTM(VA|5!o#5y&*JAcO(*Si7KN|^Q85+Hb)8C^A z$#6Slco<}O78xVg@><*6SgojrS9&h79A8Yi)`R9(c7;eU$KdrVJ#oPuuh%CoeK?*QLOFwA z7C|!$=nA^7BDgKyF$InQEd1ESTR5>YLI|gG=ZG*m@b_tE4fbhP4F<(|$Y8@2|A17f zBAgQ}@(FW9C{rAP_ob)&k?f)=f22ovMhKEs8W}M=pL#t>6F7F+vcNq@LL}^JlBi0E z({bX`c1Hxk0{uCFGA06f(yo*SM2TH$RftYs*-J%JuMRqQS^SbURn<(ZBW?sL0aB)7r1!}J*@cbI+|#GWL`*?k0mAb5pkZZBOw zAo%PEN)rpmL8ZBq9sRGcsh~?#m71SD6q=toV{=^jHr3F4yR1o#%gTDVODbcPLnZ*w zV@;hY$_Tj2GmMe^z%h)ef)BU?wm_mfK~)wjwJKnxcBI^o>q)7H{D-OLhLv_oPWFn( zj3~0}W4*+NSa0PWER6|Ud`K#oXN7ymr5wA`<(Qb}i{H%vA zT3Ty7=@}pvnn~av4G8?By1+X`_}f;QN=g?Jyh0G`%n=iGT}iN~bM_@b zNB1M@0@t4jWgzQgz^iODkLM_Sopy)=cQEZ4X8CFV&}eIfpZ0}DTOae&U>-QMT{(VQ zH;wk9?We^xS}N|RSsLxOgrD}c)|SUQdGd#7(`PtN5rN(an!?E?L-CHN%>xMr6RajU zf!bL}a1+6u1T$>aK9kzfmta6#wJApT?$4U$I;81-V+qb8*orhYq`I^E{s!h!{kdTq z@X;S>yG~;HD4-X42EllOi3F1gD)PeoX!~|=wJZMo_i$&)QlT_b&(fYi6PwP28- z58!7Cj|b^8;i~i%l)Or~H6po5$ms!UAO>LQSx%ih=mYruoxHr~=>zyj@g{UW z=UdeIoHyV+X7Dw@&$3W?{Bc>K-rOTCQ6tMqo@eVM*G)Ffb(1ad*5()J199ud7Bvue z&690=qJ{H%5PnRaTL{VgJg$>Q+_wz1ibjxNImp>XspPq!e! zBJq+Y5~1lJR!E;v%aBiSbE(LCp?85j2UESF8_8Lw*DR!)O!u+wpquT<&D?4kc9R!5 z3EjyKl8E`@bb6*{Ir}JU)*mm(_fwMF6D-X}I)THZJ-Y_jSj%T4oyL-!E5E6x?>@xcT zGHGTIJT)4=mGcp`k+_DPgj4Mzk)CafoRLI>xA0pT(@|h2x=*sjkU^)|V#WlROe3Vp zRHY(5PMH*2WLsmzRq1wdPr4ivge$B;XOBM378Mg>izvSYw;J{>C|k|(l=bJ-25RS5 z#-MX-VXh+T{ZuQ04(^vr)4poINrkGNgKED?WvKQFbC7+WsslC55??wsi2Vq8v0>o} zbG`HAD!IcJod;cRio9UYQ4n=!_VA^QU{%9R;6wKRoF>#AkImbN}P zj_m3En(=My^^nEM#$|QYHOVP-Q{xq_+{MWx-B8}?9WQ%;I_t@J!*8wc^*M3&n88KI zJ^npPdvqtx9-k8@Y5dhKeY6U7s!G-t52>zds*<};0$CiFrq)3}F*PszK@VwaZZh6C zBLbE1=3dqOl0L#AQ^joeh@kx=3+;<6v>U9b+|^d*64zv*t;|CE+TRK{1Pim9HwoS* zc!y^`@6+{jg0DOOXhX1ye~=I?=Oy4l<1~`xVl>ND58acl{Rj$kf3z=Q_z(6H@K?xl zlA#2nauxe%x|U~n0sA6ez&!s8h{eP^#?f#xpCvWqMEhof)pC2r4fHg(q|F>(oIqPo zf7Bu`qm5<94w}+Ha5cd}ZdM+({{(`Q@}j5dF>Y?L@JH%C$=BgRIli3fEck|VIV`6Q z3`FcEwT%vavTSrn#-hs&=5oRN;aR%y{dC&N9Fo@$7O1wjrw{X9&O7IC;IN0NO%&j{ zkZ!&>V#?=~1uYpt&oR|~kK%2=LHbWDCkDQCY>+mGx4^nf&OmN8dC6I>Q~-P2lD@V% z$ZFl3UK>NkI3b-|b0n9{Ma~Bz!1@7);QLvTGbI*a4Yn@M8wK438z4QUDXiviYh%Es z%GMzmX-it(0U2EoGj6ob<=oc0=>vJ=r1OV!Rg-@uXNg{M;JZYiPGsy0Fz(|=)9Q=d z*%!;GbU@1PAe?L|E}ARc8*;n;J6EA^qH$<&*S#%m`g3q$38)UT-NsVMbKadCTJEe< zA-&%Sty4Lr9huYPKy6V7=Vh#?cq~yy*fW=u+XXU8+jxz1=F2eD#Z&PeuxbC$oG*6A z%ZQF0W$T+a5b-7_?sEtufK;qVykQpG6Jxe5UTrCi71`oGtC+Ba_;X7yd!lvXVY?2t z@+28Y(}InUR*EO6KbmE3UA?% zaiR4ETgB|)v`sBaOf4}C^Nhf%>gt+mZ;vGgkD=b43wh9}meb9PPg9vzYjt2F`dC|*(&RFwYAcg|@bG5ypb2M2;+~YCCe;>i$t+1Z0;i+&v?|KN* zaqQBt!N3lE$0GpUNa)v=RDz3jC5XHm zM7Cv;4|$!O`}`q2tf^duHH~!RKNqfz0r@wJ<39;U`j?i*0Az*6fb*aPm?g59AFRm znSg%nW4f6h;TEAscM)={@I`kK_JHu!1N4}fUQg68vOV(%v;Bx8%qkWXlFJ<-xwQDJ zig3QwbLgC3_rGdD`A;VO*ooN* z75v4z`kxt${F&)#f1In8Jcn9W(FtJJw+c*+?H%yaYi*%ei_!@(u|% ze~XikJhWHZ$?GER)st{*H1Ab{qeIrLm_1HKgBY`12>ZY zquKH@9T9UD7soai3|pU|ZGNKW%Q5mpwt0S(ZJzJb%lVpK&d*|-7l#!w5(Tazj`les z(fo-YqWVjDua2B{4+K4{31>G=kqS4E2+$X zMwsW9@P=gU-uhF7V#T(C2bE%+{wZCLSDkEnkncp^KBBB~>Q<-o?kRV{?hM{PmEbim zwS@`;MrQ;2qgp2+5BMCR6${=7D*%7heKSBlGVC)3nhi=mXIm&0#0_*EH zSAkCVnXAB7`|?5?_Ak(FS|AI3Htj+_(nPBV=z}7gO`Ah^IbKTFq_^H7J-Waa zW#vR22Bo^d>MN$UOMN=D7|U-~Uumb_MoCz>D@g0bRykTPyM^12E!=w;q0hb{3%9RW zOct%xPjvcfu}C%AOT`K-Uyc4(RASh=RVs0jv}?zQZCE#sxGZS6miz45X}YEyr3Z^~ z81kqnT{+69D@SR%a#U6u_B`E&ogJ+@&+HKlq(p4qFHNPeS_z&anA?r_FVXcOf_>ex zhoPONg`YVVWLHhOfj*4Gv=C9#U-N6D%p9h{!Yv4tJ6${YXJ^2I)eru8UTaU#x@`~C zIZ~@L$8=B42-W#h)3wAybxt(>b(Uy#R+uX6I;zeErW>Lcs&iAI&dJ(4n`No@6ZOt! zTmEvD8Z9X_3(Hcxbp-bkJV5YR#DH=lM@HbM`}XC8>Fxa9CK9bIg1(!1#^xxVsGt?R zqft4ECrCe*57Ycbj^YW@H}O8*;W>&YNU!4!FN1RwPiSp`-@)a4{6Watq<8ib!9NKm zMbJyIMqp{P2_S3a_`x2k*kkNUlR0v@0Ct&D+;UoRZ3?-_xjLrvUCjqmcy$4LC5GFb zqsF2GgnXoD0LjbI%Y>rhFyQG;1~S0cWMHU-8i=V)>nhnm7;Ns-F1S0#&_|JqzBy4?Uvb$N48+4MvJ54KlD3%Cv9*VsZP<{7jm8HFj!wyjy8HOgsgKy)Usmt_>JEproaXIiYHTW< z-&B+ICTP;!8Lfr#xMyQ|-n-;Hec?u~u@BX@Qw`OOo2B)h2Pt>4hWICDc#dH`{8kQv z8s_xK#C>|l53~!F9r-=VmVA%0le0ZxT<5Ix$JvzeG7rqRj|n~_=+d1}rlo6Ng2L|6 z5f*0&zsfj|mw|YL36d#ogGq}0p?uy7=CXZtm|H~oRq<)^uMSLf~R*R+>Nyh14qTwdbpfCP)A?r>!b5s ztV$ltKHtCXalRHXH{TPq!KM+l)_3jN6x+h% z(0bvgtumAyZ}RNd4JtR(s=&LJDiiz?KVtsNEOjFHVB#4O!H1xOWojhe7#fLR_lUbv zQDRc6+%IPaX>DwYvu$!O@2POkRoKpc3GOsV9@NO0?i4rLNFFozcK;~jt{le!cAq2X zap}-^bJvmWhpcA}>3~M|niS_F52qJ*qlZoFgsb`W`D;fG8Wm#y&aFo~UpLsxUCy<< zo~bojU;GR!ccnD=V|{H*tX@1~HPaYRBk;2=H82h8s9#o5;#`DQRNI@VL;j71=x(;y znLeO8v31g6Dv%Jn4R~s?P5BVEEe|}b&OVVJ6|rs!aew*%YJS>)k-+xGzzL)SDq{6S zwHjnK&$QfQ53n~<_H^84Oo_de8akpk>40mGqyr|@G}I)i&-*n&sPk*X`;BJQ_^#*9 zGcNUw-0Kej}a zPY^rglC^lzKy3a9x%;xnPFzD0Yj0DA>R@cs2jz8=&yS82B7SB~^SFk2jnzst!WOE0 zwQ39H&4E4jjQiway(bS{BhA1i9Q}C$e=CrLp#&odrm<91(zTJ`(xak}Ru2w8QPR@J z!_PdHmLRPgxBOV`JNy|BKY+>zX+hpSyjyoq25I_>aRil~m0>pDg@+gQ?W5a#|P>AFPBZpRQH3ZQb| z?|?{;t;rzL-IwBky-fj0-lT6V*c2hwZDv+2*cKu6;e`5OBnUnP`wD{V2sX2j-$U1j z2%b8E&|k>cdBT6BC+K$?-p|#Yuc=6s>1}vF?s9=Pc>t{!(|Q^HbEYw6EL9><%11Cn zoin|XX%S}kv0kx0L0SiE|6$ljdVWK~C&T5(kb0bnqn}2{lH!|GFL7C~*fOyk2kGo5 zzKtgK>4+9Yp6@@Bu+=)_dsRbq<06u{vZ}gdXm}r>jlY{CkEJ#@N1ZsX<%Xu3cbd`5 zrnr0WfVhSjdIja#P$&3d;D!XgM({ep$1IJX(Nz#@*B;SR&;@}hXerP7Orvb;|8sXG z@Ksf3{=Ro!P97md6heSR!YV-wD8VFP!oJEfVM)N1#RORsBneA2iIu8=h@^ozDlVF6x`_T> zM6+*ps{|%HgJ<7`1A?3vwzCmTm!YGuMpsgIvA)q@s&b7dW<>P%nD1UiVX|OCro91Fl%?GRKTEbp+QEYzZj;1xwQMT>gp|ifs}X;k1ZjF}ESA zuXvqozm7YbN#(ho$4<;_oM1dV>SE2N>j9)66M^i2ZBrGYKO zZsaiuvJ(3(s(i;i2x=~tCQe~nS9OGXMDxFt=gLZi%KoDG*-F(HW?ylgJ&(x5A{ov8 z3(T;^vPF6iaWM{#sl`-gP^^pi%F3hU4YZbvB-$KYK^Y>agkDoSLlzy5WlOQ9fURf6 zn}}HLF1jBVRk_#2(xiy2l~bsC^SW5tQlf8_n&?eegY zgU#a|g5MLg@1!?|tWLVsf=n-;{MetLD@IbhaBWzD310r|X#+KK*{Yhqq=xDgbyrW5sg@xt)pBi6d zv+3{JRGO!_%^PEx;vZ8SlVP+A9BcSc_My}trj#N2`-VNZ(aX23OGmT6!X8v1u7cn= z6olG@E7-e4qB*fNjUpHlg|3K8kzZ`adyh24+&EmkVTFk|T)~lOOWedEG|MSJapM@0 z)SB3Qwzh#yU~(tzu&stSgj#y8C)nC47)`AfiK429-5(Lb>g#`DOlWV_n#v?iNDqpt~-vR_nt3gs^pCe?rJg?-&jB^OPorV`<1Ixh~Ms5Z;5-EXn$G{(K8`;HXPADv@mEzjBfLaNVGnd=3L2)ch%n%Rbv}N znBp-*Hr`wY&ZQW*W~02UhEWD8J<3=HTqF!mE&d|&X(GrHG*Wa@2e-`EMC-~=p za-TprMa`l)cx=!nc|K^`z%ua>_paKmvd|V1(`$JV^Tuo!EWADf|77nqb z1|#0Uac&(_odaV|G-#$D3YQ5z6l;q?zC%i|D9q&WQA=C8Pk|tdHgun=jmW;+q?RUP z5SmKUDJ_rF9}M>)Uz)$+_i4P3nd;8R_$JHMfj}%r3DJ~=3DNv9swYN@7JK>=*O1mM z|9SXp%l5>fG^(y=i-M5)sFzcseg7sm(~7T?vv612}s;l=B zO}|uJSt=W2*>N%U7pS3cknU@4^ERbXchLHDJTI#X!t#}~r%Km+iE~@5GU58E(mlK9 z2AgxKH21O!b1!ov5Hguf?Yl{OORF%pw2H)&y|PmDTnB7{FA}^- zaDunTG@?zq6Xa(mCk{>z*akQ7OH)~6z|323SCeU) zF>bkvbz0!(szladno>Xx@=1!E^`SXUra2h^ex#uUMFb=G2OCY#GJ-jo!N}W25pW0X zGbGYpoZ30x)b*fLu}T>0CkA(~oq*@ERhKlkm`7!fs`Sj?ZXjGhey-}`TURu}WLs=m!<&8%8cQ&$!CT{+(G%5k}T5PC-6TL`ujJiy=a z5IvtEcsVl|9SQrsfXk!L3{n%}5lZe_>4`&Yd_=_Y3V|dlG?D!cg)qJL@!074FGL9B1Ypq|reFZWLYbsqFJ1rOX9rbjQ6vg_qVX?wXG zA?%%)561{j5{&Gk=gJ&X=Nif6lRgTk<338tIEFpqzq;>2 zu!ka)p&m;;M5Ic$0DalY#1>*$cCIl@1Up7t8=ny3pB7CJ1gFI`fG=8r55_B$pCf&v z{Gcp?T<>|JpOzkOFZx4A&L?@S>~3I;?h#W7XK_nnP@CP{azEy-11Vs<(VR%Zj(^4eR!)Ut?Go;YolYs zJyO79>K7R|9Z3V5Wk45N$`&hJgQ^O@#l+Rzn_S5K)b%>mAnPeSe z^_EN4&C-sk_6f&)i=$(Yv`1aV3Mz@%zY1Fx1%;983pIc-S@ z-|X^G?FQ{^e{0}sz2j~=?^ua`u-dt>TK@b*kQ?@NwVms?pkgf@3*tKO?@yFAfcN`+p4X%|V6iEP`2cH9@N z3sEaR1?ZDjL;)j_Qk2urDHMWNM?+WC65Y^k@=f)dyA%56LB(; zng@(6=WEA{SiXMCe05t`E*f_PRI16vTL)0h9kl=7|4lgnbOOMkSk+~9OKPeUkoj8e zx`^p?$K6&BDj!XS(e_+>*%>YE12lt%^UyiKG6cC=P;4-zH!*tW#8bu zclcoNxC8SMXXzrd^K*n;&=0#gQX=~2ogt^h(xrH=X`;d?`p821l^3=8BbSeB6DgW* zlWf^uk}W&&G)2)IPYN`4--s9kih;Y1U?ag+9?)&{yfZ79THPH$tqx8YknR`|`3!yb zf^YNLfuySQ-95eE-P22|qT>${JW236f5h+T`JV(QTR{`-*|OJne~fh8js1FaQ})BV7|^|upqL@!(iFO z{Yb)KQRaVALwCwWslhuXEC%hwTPJbb(7s~6$!E#v)a9mMDQU){N^EU$iC-; zP>bE3ndWbH72k3?#tIN=im*k23Zx)*SZ%*LWzM$PVSXDCiKD3D*Ux%v-;9Sv9jX{sxe+H^LxuU{Fs1d98*fU zrE5VkjZKqc1_uk#Y8vza0agj4b){6NMMVJe8MZrf>@Z^cxn6PptB~I88wtTby*_iJJ+Cm=NiQBT%Ta< z8f79ekp5llqkk7$!4?xRUef}pw^UD&U=}N~Dgv(CkzRXR$m{C8tXF%L0`pr$u!LY` zH;tXu(({^b!9?O-#uj27M1&~8d9o|bod&-T2Ti`EdI@|qc`d;Pf}8kNKcMHH)?cJu zQ;=pslWp7Pv~AnAZJS@)n6@=-+qP}np0@3secONcapTslI8hH7Q56-H8C99*q*UmK ze9UwIRLZmbg}NxunJxgcd@Tk05xr&jOguUBOxvqg*SFTM{&z@Ib8O7pP&Gbj|HZDL zgt~EQbaxgZ3QP9A85Cw?X=^el8(Grmnx7P9K~DVIr>6g?R;21iAkHqv5~Q(WaU3*N z`aJ7l*8j~{P?Ew|(BphORRNwdQoAT*2e(YmSD;})FxP8ixK7e_KnhW^^qU- z(Q}10=iwJb9yX~;YaOvlX3idT6_Hpp9mC0oo{nM$98~T01&}wjQa`ROTz}sld=((~`ldfV;EU>^T>_ zxnpH?S{0MApul>+7;lirx>EzIgm$|<-IbpGfchnWZ}Y0g=zX^z$b$r&z9V`2rD9%` zLKMcmBH3NMcNYosr*}Y2&fYha6H{CbLQgt)M+5%*QE0+>iKNSFNNf>UIA8S^ycouP zS?sqRFWugutWA%sr5#Nk0Pd^+R-_S41N1p1+9JH%9OWbAKh6|6bsC zbk1Hw%cT8g-{J0xT z=fM+hmWzdtG6Gv0_)x_)Rkf8F#~C}1U*{E?(`y;$8y#v-J}k`J!?=2l4~w9Ww%ek< z`m!AClV|*BGiotV{S2U!>ZChnhQEqYvxMWbt51{=z5egx)_S@OW<<1QJAi{pIK-{|(ViaL~y)hIFHueT*rpF4V_C z>_+zVA!gIDaI9Zo;PGM`#0B=yMgV)}3EscqFjxCdhzq~Ovlg+S?4htX8`o(~qd5N2 zHukkVFY$Qj6@&|Af6TuvKkEw?=Nrt=pTp?pzkEgATAa^=JuYPFgTtUZTfF;+S$69Ef)Dp@ zr8DG?0FTl@6!l?)Y49zo%*GhQjvdR+{(iYL<>7W(0BMIQbKLXPz-#J)qb?)m+x@Q9 z-5e5@#d9!>pwC-t6noc$xqxpfwJvyf9E?DM?ctrY&GxZp87vb__hr6`ye)fA0yq-S z!vVi?a@|(Fkzlc%fJfwA>GERKg6Ip%Tj1}kV@W^ZBV6cDws72hx$emc@YD5?0}tC! zAb1b3{NyiV>ocbuo>Ux@4ho>jhKtL6hwhxw83P2nZuwgR;NJDi0Jp1#F%bf5;|@Fj zc8m1=ADsS7WIMyHpUgO@c>RWp=)GMKlSG0!em|0_O=woXMyQE54jh6-Uui|Cf(8zx ztzV3z(xCMCmkncsJab`p^@g_3c`ez!ytE8Ua0+Y++KxGQmo@7T9=7u%`z7WNH=&F< z3d11N+FOQJ06XML5X2fjrrl~jCdrKlE96seeklx;U*>l-!=(~OR-4}8Es`OpfR-fW zJ&(rLVWgj-JMn=CNlvv3O0|x1$?@2?NIyt|PXQG()F^7IGbWsT%C5t;SVTwMlDrdn|0T;%WANo z8HPX8F2V{i%Tej+#y%y?V9L-PmX_EP&on|;umGoipFi1%B1FcP#nyE6_cThT=2o;M z8Fcrtv!x}}Fh!gKsgfIj$1MD*dX+N8AAibzk~Vocc2% z2BBvO^&Cw#ZneKE<(3Nbj>4Z!>#u(dv?``g4V@j_)A7rd7&*XCY*)4G%@rvXyeNK1T4DM!taw{ z&~Wu=sT4~-am-*_=`wc3zq_X$GO=&8P~*8VqTQvDhRuHC`_3;>W9~JWHLk8qSLies zz2AfR3Cj6qn-mV2i`qS>XKdiz#piH%cB{76)ANIEu2aO&ef5c#|?9zbjy72i=biajO&bbAdDE=`GbdSGmuL{?et&7e> zI2Zz>L#DG$nu%3)F5lJ%^YoGV0R*+KL->LBZ>2pS>qyl_8_M(%VL!*#v41`>7zOH` z-22k`tLmObAO5(FcrH|Nz4xWn)3s!BNlZX^he!)QGF995Y*B@Q>ADwjMQj>}j~4U< zNlI$O+D&A!4mB3|e4mku!hX6YC?Y@gfZb(&fhXLi)e;^Uyutbn_r_`=-Q^ExAVCjv zI~6B|{{|oUpo7JVxL*<<;Okl48bs|^L4qcL+PsSUR1 zk%N&?NBr0K2}b;u7d)YELj&oq`8!C`@)=$9)B7J-+oK78mG|8=7+q69n$R$1I!9d$ zcm@9xGwVo<;x>Y3l2z|063dfh6rc8dVp(;Er$2>kgP=>XUFek3?onc0#FR3Mj}UO1 zRUrnYep{+M_dNgvhx#!(ih~ZbkS`SjW^R;vhMxsqs|=1D*9MXu$HO*Ih+yE0j7YEC zw3*tFIYyaq5D5~p8;)RRtQOei9e+u(ky`%1{d2%lgw>3o9xpBoFCN`73$o(V@$R*XURxXXc5^=A!;3=sT0t9;T+=fa6f#GTA&x9sTa^|<1E%y zM8Gd;dNl6Sl&wU3&VXV?rV*vq{)iM)OstK8>6#nStKv>Kr}U39EW_Re7X%t7dk>7N zPAEJ-sfIT*Oc*2_m??u0$|!G9Ps;1mC9KAt5T<~FQ^e9}grt-6oROzz`!Kq#E%M|w z09+!$338NgW`-MK=3S87(R|w}ZQjYO_h+U0`uw`02H`YoI}4z)tY+}_(oF@Bo$mT-g2A^F^V8GbEM=d?}Ci#r+K?fz~zAiD{RGYmFMaFeyv)depu;y@f z)4~zcI=d17E&ym>>`8x)*^&JM-0c35An>RWhpv`S&=zwvEhYXOD?g3uWF(K3TFKUKiBvnQN=;LF@)(M|F zvHTZf-EJHrT`>$=iEkn*C$;C58>;#CM^&--srAEg?!8O)gS9OY(WDMQo>BQR$Z;V}aqK|qf{33AZQm-(- z0rg2;$&X(Darv>kCXlx``Rd-ifMasKiZ`FuX~jpJLlbbnP4uMb<5Wt43Ibx$(N0>ii_Rx-+q;KtTn zq|N&H-gX#%Ibl7PS47)h{Vwl;@&*LAOkAk&K3D6!JfK8y1_~?bwxHJU*Ov8#n>_%Q z8#+#^IePC$s>BEKW@Xpt{K}?))+BMkfc1#hok=Y)p6+wPGoXEg5?D5U4E=dD zQf_f|X4cP-eHuW9e^?|L2)l%vcP%b@qyAEy7&ZoPF1PF|0Y&)=MJBETIpon)oQbKGGH``ZL`3JAF}i3aN@h= z>DRDQ#Ov?cn_8~|=c=ycun{6mBE!Qsm*Fhw84wZI7v&Ck4Ea+I?NKeJ&D_tK zbaNMl5S9&78APaa_svfq=&Bzxw zN!#|G)jXxz%+GI)cK2 zErYX4) zS?3_H&^e5wuHec|Z1oW(PE|o|Pkv z&Y_Yl=!449+9cVP!jy}4k{m?aQ|NPnhd4#2L$%$MRv+h)iI3U%GwS~c+bON9$RN%} z;c@}npMgnc5t%aAJ`_7yX&2NAw9XL`>tw-V^jYl?nC*C+^8ND>CS^0LsI;RiDZOt5R$)U;!MxD5~>I|wF(le_Oa*>qj<#q6S(l8yJ)AG${nmHJ$(BzK;2dT z*2Nb1<8SA)m`igq8!_lJBP;OZ|CU|JC#^F36MdNv+M5Di!#t}?4#3a$^^YpM!RD0G zqACo3{B%Ao5Dlv|_0EJ3YY>GJdcO&#fZ%txwcgVAV|6-y-rzu`!Hd|$(|7mTGUSP@ zmR>c4OtW|4!frgUNa&{fIQ(bcmeH~7kuZDh^!K(vJu>+F`Z(jkPXOF=twv}~U{WQj zT&z$SY$Dj0j^kzjJTxkXeO!Cbm(v1|qXn|5om^Ar2GJ2S)H+k!@Dr&${LXQ~ur`Ey zhh=R^Kcp_Jzw@mWX7w4Pqa~Yr6i>t2Zdu1l%u$v5b=)+ypZqD#YZRTnw9YEb=6b8I zs<%*HL2w%jF~9Y0+EI2s;&XQH`yI{5(Fb*9Z}2OE$6n!WA7tZNi!zx|#;&wZ5}`ox zj-5*=w+YQO?X!L1PrdN$clA`Q3G7&S?49z{D z)P|wr2&x?n2^=H)5wSD#XuT2Z8r&?!f3nEaT3rDDgAL-Ffr5iACCq04ZZ3D`QWog3_T) zW$muxyQ6NsOMOpcPpyK|sZHfy7bO?aPXDA$zvxKu=jQ78E-98xV`G$n*j*F&f!~zXW1acYXRN z1R}k`Giis%E1ULeRe4HB6efM+pS_IypdiAG;S0*intx@tp!y@3O$aB|R0=Ga`gFQt zHS09oS{HlC_V*x~j7R_Rte#g>p}PPocw#&48g}vWOt+G)f)@?jIxASPp2a@4v-__- z_XX!L-C9RXWPL|@*xEpg~Ew`mL1_gKfl z!`_iqQ}E)?&w2ITb?SIzU!FRDn3Zl@@?SgNpd1oF(ikw}CTubMl(RF?C9}^>_K&QT zG}n~M1{38Z-^@Mw6FN%?D_Nv2WI(#nA|%H!uiYdHw$*SeJk-COy4dfx-hGJ-n#N1V zR`-V^a^NVpAV(-Kcj};y$(c(SP-LI&G?7&opcYt_Qp6dn+97_=$S4V!*&_w87M}RU zz=3U$$T>=yV_zYMP-!?Z+)~-y)zI5p&}E*d*q#GGww(-E=jcDYD&r3gOO??hT{=%D zvYpJ!Y9yOhOOK~9uhj1o>sDL)8KoDZf`@w?hjYE(K(J9K=%*`~Y)GPJFTq3xx>8s+6{^P&2Z*j{Zrfjlh8x=5}l?3`N_byooIqajLSy*-i@#hrAqh{ZRI@wymka5evVE~?fQZ84o* zWGrY>c;rC}n=PD=5&IV8nA$|0pJbO+O+3g94>Vs_>KCHyD_1Qcx>BTZfMF9{H^tar zhF)wH4PWp4GOdZblt?_V_s(yvLOFnZ-91dI&>_k9Q;>7rZeTZ% zod42C(MOG~LR^GWtfM)Vh#+hkpU-1pwNVz-#fxAYhl2B5Ar0}HI*S}v;?6DIB9%ZN z3nl3AJ5xfZlsUG$1r;>Q@-o4A4k)aD1_^j zrgl&Cnf|#4x3?f7F2WIFVmTd@&D7>7B~`$DpNrE%2N0VgvmvmS(Mq7F7)~8U7EFT4 z_bKaF5@S4oRp$+ zQW2HzB3T_9^NIl!{v&12hr!$<=4GgEnI)|Ib(QDi2Xi0liPv-1!=10;TEJbs4KHsf zuYQYwzQu@0RQ5`zhEGluI9p#vVFQD>H93z+B>=Ovz7@0kD#&uU=}EMp-YULOLzm=* zrT`H^XeV#1UOM3hN5Qnf_Rxa~+RzKJn}i+l;BKj`m?}w=q)c|h zE~bX5Q@0N|t4t$a1W*)A?y1E?&e-xB^;t?m7ZKK?rV9xT^J)T~ygwElGCh)&vVmcl zCrrWDnvCT)g50|FaTf#5-68j|3sg#(+O^75V!MPT$)POp16_I)5jGnn%@KD+Wn}`d zB#JV;GMzHMGM_T2GD%`<;Ra|HBqyTduRY#A&wvM@6X2Qu9)SM8UmMPzH81`z-G{vl z$W=um;(hIy->Si?QL168abUXej%ImP*2DA#K`=0=06-t-HF{Y30e}XJbP6kK)y{D1GTLpK{2Fj zlKszc)6Xx?buqyMB%*hh%cw-)YvpPrPqo20MF>4nW2=7U2lm<*f_wOU| ze8VhRRp+{ww7s9WU@jZ{l z77A5zM4Gg=;{v*srqp$^(P_?Y79)P1L;*w5ueMi>4Gao$^h1*d_6Hj)D2B=;#Vl`+ z%imGcY2lx*ebW}dW`{*(bMzGJ3z)LMQG?^Q5-VM${WW~mzhvp(XNs7_HB6@@UCf3- z=`|v^WL1_&;>jl|G_E$yj-h%zrU6r9$l^s@AYXFq#(Zj71D+ZcU8UrFmnp85)oHme zb1w>5-uvOvG%7I<+1swEM?Yg~-myA&0-w1b7FZVgKN|K~caM2IM{c%_#xKg=UyUn5 z?&VCKGm@l5$kx3-Pf6-iBm7IP!x`(Wu-(>rIcJ}vuC0FN!@Y~ET>=859ki_qHefcb zKjm|gR+`-HQxkdYMn_w!xq*bq6_+t0md!u0{*A^R=d&~=I#wIxx^sVwwMx{m%=;gb zy_3!Gp9_<4)p0cNNnC94ZBG@kDxIg088A8IF6)dvFK1-v*=EY!XuF`>FVAx)*~#e_ z8p>a?DX#woT{Qfd%>?W5mFi8JgM**db^dw;;IXwA(Ta+}{pfo@4b*@LzrUZ)R z4)5dSf+e#(yW6i>=$)hXd{4Ue#TaEZpajy-%!vY4o?$D^FP|%H9Rzl-Ov>3EzW8ht zx3Uc%3*?;5J2EK}QerE%Hx#x5WawHC*;a;Oo-77os3%f-gY@T@2HPo0=ji7r|6)pV zv@>t~Ta{fgAs3a4ApLALdd7Zg&+({Jra$y7X}T8z+D2x--<2@sndv#jv3Q%;WSMmh zr!7NfgX{|lJYl^?2a^uegL1Sw&QdlAF}i3?C*zQFGC%nG>87Y0?PJ=?y=U*gO>vSuZ{7%c5cWFK`Qz87b^ zv5hF|whXGFrF(w>j^Ih!)=KN%4m!|Y{RxS)VHBsIjfnI+9#W|HF9Mhbbtw0H{S(-4 zkSV6e6@PIqG+OW*Q2t&+5^N^*GA^$;nQS$jrTa`QOnzJWtV@|3uESZIkLs!_kCPuW z!k&rk`q;LoaL#7EEvmhB(dXe1DW&;C4NM)uK60T=3#55mBGcSm(`ByY=6cL{;(qe= zFivmYmt4C4G25IMQOT$l_4YP4b?M;`;nqT}O^;b&uO#*(1HU_|^nZb*F=7z#jj{6( z8+q&UmqEf=+%U2r+N*u1dC`RSn0!5#c!-;Ocq5cnZfj(4%;jPM)E7HSd^-GP*aEN0 zRJhV9o90lAj$B_h*fO$OE1u{vdF#H$oR^Gk86~#vbU|d*m>c?OyP!~47NvnCKc)~P zvN_ll)N37YRjo8;V|`SS`8m7V#vDIcUL#+_oU}yCRRA2mnhr8oo5)O@@^!usggG#p z=#Io8;%~Q5X^bX?+89qwDr4kg{{0%&#Rjn0WV-~^JSANQuyAa2G1XQzDW^A_oSO|& zq2@KUYo{wFD3Ko#EnEhX#A!#MZECxSeVd)X>55Q(F5_hAFVE=6f}&I{h6VOm<;|(0 z>6%oyz_ajn$JxUEtWYPd_BgFZ@23otZO90S!E`@a{ZpD*et@4uF%JA{+}hs9EKSYF zo`0Dep4IL-m-~L63R$Ur!3K`+*`>k38b4FM4AIFP2fHY73v#u1+$vp^R4c^x^w{vP zwM+>kz?h<(tp&qQngIO)L1%6V^1VJ_FSIT3UD}F2b3Ox_)Xp~><@+&9NhKjr<2cs% zH9JceQ)ALshcJm|e>c5L)rexhU>BJ98Qc{=O_i{G?&8ys#LybsBHu-9NH#L!4%=(9 zy~?3%HoMuKf_-({<e6V(-5H(LC>3V;O6fsTZ z>ll-L+=?8Gt%E_){CrGIP2})*YP&^DY=z{5pTTnV$Emw}Mv)Yu97>{K059P_8M&1{ zwh?InEz|)MaU=-LE0*gJcq%a39TXlW1iE7yx|5hv7SkbhPi*kv9W7ie>^bK-b_#YF zAHO-K9==&`h4+V>JK-6lg}?1?O-F0xx!=?BOGWLu`jeir+eoywmyC~`2Cy4*R)0pL zsQG>*{olQ3_(JM?r=!FB*v0Nln;VSaev}X`)D8R}YRF)wOb`81>5m>e`y0lz1$>Ac zfs|uH_X~p~>8q%ZlHLq=Y3tFYlbKkeixP?pjK!{3*WD9-eP0u^?}#$JsF~_n{Oqt2 ztv~g+T`gbliCw6sjXnCDcU!j=S5iCp4BRoAu|s;CoeltgTM%?i+taHpuyIHCw4+Zj*9H``a0%R5vlrL6d!_z;Wj5mv=qb4#B0=~?PKf%X*Fz5Y8Q=Dvw z4>QWeJMGo2%N4Do9m~CZiLPy25b-6iA;iMyV-+s6qz?K;U80^RrZK5fz1_Y(z; zhXY zpQb7|PLW)k31zV_e|~!^yxgzF90GbgnF!byA+q^5I&&tvb^uA0-WL<4Hh2C_-tPRe z*FH223_s_fD%C~AtHkobk~a#b4L)w}UJ+xg5sp*XqA17!CcY?8NAYwP340Q^-Ckk? z6Aaex{)yT&cu$)fH%-+RTX`XV(iUD-KmkV=ymf}tV366&Be_n!BNtVyc7Cb|UxTt| zx%)o@Qe$%MkcmX(IsSSP5Q9FO~KQls6z>8-$D`&61a@K+Ih-lXKZ#x*P0+ns?t zO}>%bTq9Jd9bH>vE6(0b^i~ch?g0W0V{baM$K-TQbF07IlocMze_hf8nv)Pe@A7ks z)cjV8BBi~8raNgVhq_>4AUzi{vXYZuX*-RE*Z#R_4Fc9PyXHGz0^b%M-f>pB0!EVV zWAlRuWan;;0t#)z9Pqe%7C*TU5epgsPY&-!lCT)Z<<7dwcXTVmdx~3Z?r`=A1K+Cm zH}lWGoi_R4%jGL)A9-yb3-|Y0tfas`XiM#%9A>KBt+R~#ioT^B>I4o7q1&-?gA(Yy z{R5dsZRzrKGY1bUMXG^&qwcl)bK;CkVhMVdok(5D)6Lr~+R^)YBU)RFNq?fYnw?hb zx>no%@@hh;XXjT0(CUPSIPSGP*vk+29MhS$fdUv)_1|;p9%(ho2QoeP09x;nMd*9K zw+TD4YS6xEb*=l_KiD1aJg6`O<0e*>P!)MKo*}1G4GnsxrWJrI@=9x_;N(y27utY% z6bm{h%Lp4frkUQS7z*ZeBVW7^X7}18IYcZ4kf=Qh}4EP`7Y-ilZd>#HkDwN7BhwVm}tX zTSX@xeiDI(44?!2h`z>ar5YST(#4Os++RnQxqx3LoG(E?jiD&+Sc z&(5gD-vLpnqZm(t~`Yl$A4TmQ=f zkvel8%yJH%t=DbHUy@aB_r%F9wR&Ja&d73xmv2|PpOmoLD3gs)rWVRJiu%0AXig%} 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�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 + diff --git a/build/windows-386/README.md b/build/windows-386/README.md new file mode 100644 index 0000000..2b2c8e8 --- /dev/null +++ b/build/windows-386/README.md @@ -0,0 +1,144 @@ +# DNSCrypt Go + +Golang-implementation of the [DNSCrypt v2 protocol](https://dnscrypt.info/protocol). + +This repo includes everything you need to work with DNSCrypt. You can run your own resolver, make DNS lookups to other DNSCrypt resolvers, and you can use it as a library in your own projects. + +* [Command-line tool](#commandline) + * [Running a server](#runningserver) + * [Making lookups](#lookup) +* [Programming interface](#api) + * [Client](#client) + * [Server](#server) + +## Command-line tool + +`dnscrypt` is a helper tool that can work as a DNSCrypt client or server. + +Please note, that even though this tool can work as a server, it's purpose is merely testing. Use [dnsproxy](https://github.com/AdguardTeam/dnsproxy) or [AdGuard Home](https://github.com/AdguardTeam/AdGuardHome) for real-life purposes. + +### Running a server + +Generate a configuration file for running a DNSCrypt server: + +```shell script +./dnscrypt generate --provider-name=2.dnscrypt-cert.example.org --out=config.yaml +``` + +It will generate a configuration file that looks like this: + +```yaml +provider_name: 2.dnscrypt-cert.example.org +public_key: F11DDBCC4817E543845FDDD4CB881849B64226F3DE397625669D87B919BC4FB0 +private_key: 5752095FFA56D963569951AFE70FE1690F378D13D8AD6F8054DFAA100907F8B6F11DDBCC4817E543845FDDD4CB881849B64226F3DE397625669D87B919BC4FB0 +resolver_secret: 9E46E79FEB3AB3D45F4EB3EA957DEAF5D9639A0179F1850AFABA7E58F87C74C4 +resolver_public: 9327C5E64783E19C339BD6B680A56DB85521CC6E4E0CA5DF5274E2D3CE026C6B +es_version: 1 +certificate_ttl: 0s +``` + +* `provider_name` - DNSCrypt resolver name. +* `public_key`, `private_key` - keypair that is used by the DNSCrypt resolver to sign the certificate. +* `resolver_secret`, `resolver_public` - keypair that is used by the DNSCrypt resolver to encrypt and decrypt messages. +* `es_version` - crypto to use. Can be `1` (XSalsa20Poly1305) or `2` (XChacha20Poly1305). +* `certificate_ttl` - certificate time-to-live. By default it's set to `0` and in this case 1-year cert is generated. The certificate is generated on `dnscrypt` start-up and it will only be valid for the specified amount of time. You should periodically restart `dnscrypt` to rotate the cert. + +This configuration file can be used to run a DNSCrypt forwarding server: + +```shell script +./dnscrypt server --config=config.yaml --forward=94.140.14.140:53 +``` + +Now you can go to https://dnscrypt.info/stamps and use `provider_name` and `public_key` from this configuration to generate a DNS stamp. Here's how it looks like for a server running on `127.0.0.1:443`: + +``` +sdns://AQcAAAAAAAAADTEyNy4wLjAuMTo0NDMg8R3bzEgX5UOEX93Uy4gYSbZCJvPeOXYlZp2HuRm8T7AbMi5kbnNjcnlwdC1jZXJ0LmV4YW1wbGUub3Jn +``` + +### Making lookups + +You can use that stamp to send a DNSCrypt request to your server: + +``` +./dnscrypt lookup-stamp \ + --stamp=sdns://AQcAAAAAAAAADTEyNy4wLjAuMTo0NDMg8R3bzEgX5UOEX93Uy4gYSbZCJvPeOXYlZp2HuRm8T7AbMi5kbnNjcnlwdC1jZXJ0LmV4YW1wbGUub3Jn \ + --domain=example.org \ + --type=a +``` + +You can also send a DNSCrypt request using a command that does not require stamps: + +``` +./dnscrypt lookup \ + --provider-name=2.dnscrypt-cert.opendns.com \ + --public-key=b7351140206f225d3e2bd822d7fd691ea1c33cc8d6668d0cbe04bfabca43fb79 \ + --addr=208.67.220.220 \ + --domain=example.org \ + --type=a +``` + +## Programming interface + +### Client + +```go +// AdGuard DNS stamp +stampStr := "sdns://AQIAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20" + +// Initializing the DNSCrypt client +c := dnscrypt.Client{Net: "udp", Timeout: 10 * time.Second} + +// Fetching and validating the server certificate +resolverInfo, err := client.Dial(stampStr) +if err != nil { + return err +} + +// Create a DNS request +req := dns.Msg{} +req.Id = dns.Id() +req.RecursionDesired = true +req.Question = []dns.Question{ + {Name: "google-public-dns-a.google.com.", Qtype: dns.TypeA, Qclass: dns.ClassINET}, +} + +// Get the DNS response +reply, err := c.Exchange(&req, resolverInfo) +``` + +## Server + +```go +// Prepare the test DNSCrypt server config +rc, err := dnscrypt.GenerateResolverConfig("example.org", nil) +if err != nil { + return err +} + +cert, err := rc.CreateCert() +if err != nil { + return err +} + +s := &dnscrypt.Server{ + ProviderName: rc.ProviderName, + ResolverCert: cert, + Handler: dnscrypt.DefaultHandler, +} + +// Prepare TCP listener +tcpConn, err := net.ListenTCP("tcp", &net.TCPAddr{IP: net.IPv4zero, Port: 443}) +if err != nil { + return err +} + +// Prepare UDP listener +udpConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 443}) +if err != nil { + return err +} + +// Start the server +go s.ServeUDP(udpConn) +go s.ServeTCP(tcpConn) +``` diff --git a/build/windows-386/dnscrypt.exe b/build/windows-386/dnscrypt.exe new file mode 100644 index 0000000000000000000000000000000000000000..b068e54e4532f636050cc5a5c0e433fe28fc85cb GIT binary patch literal 360398 zcmcfq34Bvk+6Ru`b91va-2xOWZPCJ1ra)Fkz{*!pfVWis0wu^g7F1i$*Q0l zPgc%}Nv7#)JmQaLO-P)9Dk8DY{^Kb>Ha^ywNXEN^Nr1AZN9J_;lihLEI!#T)qCIN7 zZEh+$eQx&nXaucI_Rom~v!=w7tf_I-DTHk8ibr}-9*EYqRMa#* z8IN?%$(|RFBvnFFLxxZysi=wInEGEcWqg#FG{Y1%Y4$}r)L3`Y($pRI(&ttD2BBPJ{p<_FvTopV&y zVVInlQ;>n&5mD#Pp|#D4YwJTz<>&>lmYqtF9uzZls!1!KlheD^_yXev^h7~_2>tBB zva4pblTWfar1CH;tR{o)v~ot8JDEQXSk_=%O=8aT#TZgclq=GWp%A*0$Z3nmdLkh; zJ_Tb+%>~t#HND**S3{`b)L*k4M4^5VIaZ|K%<=7h%#PFE7K<)$RNHH8GpGBbi41^w zM(5nl*u2gOzoIE^KGe`GK3QncBG$NVu5n7NGpL#;h#6xSeyJ|#3u@8xPx%$C%uzd4khqGv3l{i0qGN`2 zX$v~56DvBbp9bU`bk z^b!e2g6N3P*Q%8Zrh&7P$>=cac#v^YC0sck%){_`YoPjYWKOoo%#thu zmZOh~$kYJdH}+S|74~N+s>X>x_#SAQGF^_(qmJ<9`8{!>cvfpH=#SFwQ0M_h+7U~t z6t!>!Y;N;JQPHnqQ-ZLl3p|D{umo{=Ol7|4A5{*5oU58?0)6w7mLBlp5N597SBzP- zH!B(JN)6jLzKumKt&v2MXlH#ttn%d0xqk7Hg0EKzlWxagBFM=7>5V|#2&l^=WC7TiW^o)VrLZ7Nw}{w5ijkPW4qK z;=!uGf~1-dPfP*xpPZT5TU_a43wXu}7j*lh29P|PZxUF>`SZ7Jb{S)Hw{5O5@>)%T zsVXGkl2vH7-YDW{CWRe~zlex{?Vi7jp5bh-M{pk!?`p@xsgwk({cc z2lX|SJ;mP{!)}a$ByuK4g7J(*G#b%z%-~amVV)f6?8ab?6Jy=+gvA%f41;Wp(_-D| z6_B@Wt~Nu0Aat2JBAt|M2P(qWBS~vctf~uAt0NZ5R8acLy@nn~rM=QY;!Oxb^gBbs zK){N<2A#w-(Vu8n6H1#u*=~f`=}Hs|!Y6SU@9;+nMmz%}Kr|W4R}h z1qO`Qj15A~K@myn?Xn9QgtMB21_#ZIqX%Ol)flJe04hz9W}4FEgm^qgDqeC%7jY0! zO0s2sI1XYU4NvY&C>?>=d;&KDOra07sraMV0pae@9Mwv+NaU)9sHF+$lbv>F0@|S* zikVsxynt_d%W3A81kW~1ZSz^WVqH9e)eg=zU`nkN8P^>St8qi1J6uAdS0sNA1z4YD zOzUKSS2mPkKIus2WyFzX(UYd`PO3rNjF#vA=WL&O+&XSmcwIOo~Z?U8hSfPsvec+aH-X!QU%~f1eRMu7M)j`rgjm9^x^WyyFrrJ zjHK7url~;+xT`hI2T%dQzYTX#D|QW6muK9lkG7`SE%AWD(BHMQ@ zyA4Sj)|NzTEY=mk7SC=;Oa^a+c9}>HZb`I|T^{xaRbE{xrvL>aopXBw`)2@Wbn+VV zTN0;5V*!6uOB;MKm*R^#YGL?e> z0m=uf4Yt$xoB8ldG6$*Y7$U$}3oW^}6aF>6URRN6%%GZ>3z>2g2RKeay_d;F0o?MUS;H6H6n`w=byuCIq+ z+WT31e=znj!-An$2ih6k3C1zZcsgxF)9FFli3%}W12*EX7OQ9gfP0OLX{JCbPX?t*^-VoQ!Y``Dnz&8#52&~86IzRcm}a7Z`Y zJTA{L^W?_y#4ij82k{Kz=%d@!`D3)4VMg99%T!EV90DbaM`-d+=#8%<4C`ad9&eW~ z4Cw<$#|^Xo4}-rDatd|)YRPn=&gy^l^sAU0^Z+73GDWx?G=PyQVYv2`agk)ge0nGd z^*3p5>=faYgqDu;u<6$d?aVc5{W>>y63RU) zNwyn8{sqdJ02-S73(UV))wuweTc%H?s4@=^605GJwk(rgsO-2&r8owVN>Uh7$^!pa;4W?v6&41xOoa_{JM)2BUIIrvk1&-$DGRaA=ll}iJ${BV@5#UbjYUp z<{;dRYN*v1h(kYs?-ZUJV*??+Em(z zLZ=`bGsr6kKf&Cqy~H}-e-~m7A~0XkV};^!a()(tJ~#rfPMnchd9Ps0#*-EvozNM| z;t^7z40F^Z_Ecjh%dwK+pq zI3zbWvPJgtjD;ABK5;0=z5V53Za`nL&Ct@NFfPEFWB-*=^I2-9Sw8!RU1xfp* zg|PA%r%5^Cm*KLGbVia`0bYqAn4Hf^jwTK;QjFxNUyl1tey;x-z%%=745F=6Z?l8R zcyx+Tnl02Jt`CLCZBIGPojGuNKsdm{SN$Dk(hj4sIe7vIZs4DTvl|odV4S*;z(LCi znX?m1ROuqEmm&&yC_@@~aGGhmET15?VIKut*i1Jds z3=Nbbl0t*9&-|grq)^oxljKI4Ynli3EU3aemK3VH@Mu60_jeJk#=4B-+u@o4qYkOO zr;`!sMV(p$shL$UVId=oYCJjjF?I347^I3&9W8cuBxINq2`SSTbOr%J&$B@gx;2xJ zICWM}+mPl{tplvse8IVAkhk*0BPN{&n=wanb4W>GpeG%HM%@d;(wUV=;z$guA)J$a zs)KTz%&1Pm=1TT*qY*Qzq6aF0wCFIx_JyN=90?raUEGeMy>3j#=Bk~fKy<50G8PXS zp?6OADYODcC=u#>020X%p54jdZ!`PbYfJTJ@Jcw;(QOWgCc_;!-#C3iA{>JEi@eVU z__(sj&BX<}VP33D?h3(M5{<%!4jOp@C4u?3Kl!MvcP$rQ?nvOG`rRl3bG9v~Del2~(R*kpxk;0xLaap;h@EBBPHvtT1&a zW44XW1nA{P?K2e}G;pHs2##LVu!+v9)G+TqMDbicrWu{I8d;nS2`G*N1+?OrJ)-HF z1@8;7U^H^8^@JlgC`+*g z*);uNojAZy;i^PJNOTQkY7;w*Z;qZUK#9jrn4cxs;M7j|>iqK({yem!o5ziv3s@`* z@|XZ>GN&2KLK27;EBE)2&T1#Aw4{+xcWGyy20a6rJ}~kkEv`9INmU%TK+xL>dW3K@ z^2y0SL}5Y5`^gy}jQg@*o_S)`(AqKMq^M}A7O3A*wMUH_{P8*DeN4gCq>E$;DexK$ z2KpS>dLlT+5^LxgG)D%41~sD##8BaINMhSk0JGHc`{|7>v0yT)L}Ec~;?6*KSP2Fq zVH4D05r>x~7=T&sx)iOVW2#1va}u4)NknMm(ZnxF|7!Ct#n0}k@c zAOcc7D5Qn8(vUhsBniN*05o!Gtq{PB2YIZFEs3T`+yI7@)smQ|&gqW&1kp%G7y z#^%AZOi`;CIzez!KQB9D2OHY_p@#5@riYL28LN%Ts8|IJFbjAC`*O z8O_lnglH0C$tnU4nK+>bN_rM|*O6kE#qr6FXX(Vj88J4vn3C=024*K6{%gfAAG4u^M=GAAT7$3_7X&2Yo1Az!Rn ze`VtY09!i2#v&oAg}mAPRi1$v@bv3D{nJqRQK0m6bme7$Fo=HXB*-xYeoKAQTHxy`qeFY7^|1~*O@&c~F6gxil$?p` zd|@@?MG{H~V;YHpGeB1mYATnckiGoq*{r=eL_Q+$q>e6l9wF_`8s9jvd}N=);t`eD zsp81~q@!l1kML0(-Xnc{d_To|7e7jNy7*x3e2rmbPyCX0CSQX?VDNLZj5K$H6{YieTDW#F|FL)`DxDkVrBUf@ zE~|Vb4&+8uHpL3G*n~{~TCN5Zey1EQg>E#(PDm$u73r6TYreP)h(_(`1aDPC(34v3 zfbx;4Q=bu)qG_PeDWaZ4l@NxV8LMN}Au()1?Y*u_e$+iMmCQUN&xEEA%WWCB{It9v z1xP!a5@{^qh)VQJVd?I5X|mVQ(9clH|5^H(y%uY;RG%ez<-+l$^ql=0JZ$Frb& z1U(nA3IeTQgk`&q0_m^J3#U#$=>zsTlUB%|Ghb1yRArVQW4AD6Jv(2LXL_Th-T@01 zvA=Nu^F6|%tlT?5x@ZmCDsb3LuXLt;r^pYz&N^ArD@m`jk{<-fvsg|@QeElwGOvFnQ#CxCi9h= z>I}1QS6-Cs`=Wd*x8E;N+MvQI&*08@NGfkrjw6XB1nB*mXR1HDb5%*lNT!^4K+qJnM~B7QUkkF|Ues$~`8&nQe>MVKZhf z=PT3q6qf&B`U#=5!t7VvgG-q+1s*G&Ax&dniaXXTB1$2neRyFO5Wjmv*jW}BsFb=g^1=;i=6WTvq?W$ zz__r}ug(HDvq^u>0&BBL4`<0a=-G=%eUOFye=tkB-s)wWt-hj38!SJdSINt*$~D%1 zSS9%~=A~_L2YZvi(F!eE8WqU1NiCM?IYP}{~E?b-Z zTz2b~mgi+=M~z^)uUcQsmbPSXRDfl$PI=XOZ?=TIgW02%t%$i0b0|BrSsAectT*Nm|Qhg9CA}#(~VYTjW+TtWQ}hI1pvBR!o7R zg0sTgEi^;#v6<_&`l7_0w&d94CQUT5p2Vfbee%;eSLBL{+OElcEr(GJtJpQ{ z76Fz2z}Y40;5py2iyp}RI$hvBQ9$x~z?6t`#}LD!NIlUK!c#Pc*1%H}InpFA52n%c z$~-S^wc4k1mgc<(5h5$Ql)ZTr-jw$|Pd}>MZlMXx^RiX>3$1~tbBf#7F!yiWC$V*T zOY`o{Uc)5X?+dLx$ciFsl$Nzs&+-*HpU&BzXOJuI%~sasU7Ig4(Br}E%UQuz9k7A` zSF(bg0>J)|T5HKWZl7}M^V0>XtCrNAeD0M8w&~Jdk^z z%^L zPLAU%x)T^tCKqnXS02glWWLAp@9odpPz7j%dQq~46+(sDmM?voe>h*d%(gAxSM)v= zQskU}=PU2$CnVPHy|;g)1XUo#&Uq|<(*Wi}75A}G8P(tFznSbe(Ig^UH-TR${B6F|Abp@m-bsL7ueXP{aOpC;Lv|4 zYvse<8=sa&%a185DYJmB>^GTbp8QWebD_LlF}0020p)UAeF#_f%QcqdemCSfwZEwQ zdDMNKsQWs-?w3&fo7}^v%|zvU`c(c}DtDp$rl|e8jM~d{53_$-J{u&e--qhgir%f& zsz3QNRPeQ-+5Y!+sQ*Bp`u~&8UMPR9n0zCO*;+I(Q|rDf_i&M8>61m&z=hW3g|~t( z#}=^v7>qfPJ_`pcm>AOA-UEEoH5X z`|+08#57d$UPJr9dn5BI(>`O8_a^2wod_OsGeQ~RI_5p$Th<_XZ$m^bk-WES;hoIu z%_WN4fQTfU<@*Lpf9em7`(ZrH_-Flz-O=x0KhA#^^+zs@kjwi&Kw+V#5nkP&=U&@? z6G4>+`~L$W+b;WxzC+o^1{>uE8D?vf;fKKztO{HAU8bG=wIup&yOy1^M^8gU+ZMB{ z+55c#m$3ah06Z7X34Jz?%L5yKLKDUeKns?n%>xb@q+NK(KNLytQg}(R`H;b6eb{ig z|K`j#e+$6ngN^+fp(+&AP6EnFfUF+uZUV~c!52;j`x@iX zfX0E}7;YHMq=K_PHvscAV7@Wf9R=nagA0Z#9PpU{d^VWKui$ixyBJtM3^r!1qYHI9L}zvlXlYnnY;+BT5?nA=Y0P*31{LfYU@T`+qYnux zLJ3atS27E7DOx<{eMS{?^uHAV4P3O~L`7?#?JZcCrD(VX&Yysr>e)etLCFP`pAt|E z->yoovYd0a?;xMaCS9RB(~v9)&_Xi`WAL#Tc(IdZ~#0ePDr8aaSSd^`a4NZ0kU4 z!Ds~|y1wYcBCI(reL=36k&g%D*1K&0eLMiX`TC+u&=+ap1iih-?EFqF#wg$(EMmI{qV@|JXoIkgHRy2vi;JV9rB%fP21@MWVzvtXD)x~Aguz@o z$T<4(L0(bqLMvhGIN*;9-!DDfzqkpwM3YyXFs;I{6I7=4!B?~i+igYhF!>Oc@^1yo zmICO#t&h^v167lF*^>e)&wR1KR{j#Erkpa?vx-+V^fqJ&;DYjZG-O_dXKw*xq_INo zmVBXM&reqlX}n~hXfmUYiWLH;Or!>{8JJc7r-3%x%>!*v&r#Kvtp3khAwm!hUr_$n zj6yjN479oaKG5RXI?xCw2nt;;yLJi$xpntIOT!zO$1>3|&s!oxVy)gC#gpxy>d7|m zuHw^OhxDWm$r)Sw9q#{=29YOTJSb|1ny>&%AV%R^Jt!)>G_bUV*{>5BhMO32><yX?lCmp5hA1**l1_a?(F`vJcsQt?nk|Fc(1=key2lMP0T{ zzMFj|P$U-hN*(1(PS7$NysUAJVVRz6!gwd-Tp^OIY4r(MV?GCwtCeeYDD^>-Caf*H z=>$kNK@A_Cz$9t%gilZSYoYRBAxbrEDg+;vCRA+E0Br(rdc~6(z$E}R^-pVni2~4K ze^~?g1YkzRdm5l#0LFUuYJgOwUu%F?fib!K=iWsmuXC&QfHMB{8$iR7*R|Ekt&UoR zkTxF5^G=cH!)76i@6XCeMQ^Q=KqYK_GLJMZYlmAHJ5vBCo9*3p3A&8U(s!Z zF_JJ0)@3=j6kl8-!TtB5T<9GzHr>LGk>%NjLB`^*Ecz#s2TzE$byoa#ct{SLc)PkML8XB}_ICoej->O_Sj_Q6FZ4w6t~TvcM%^HfTMoVT8pMfi zuhYUc6cXS@J#v#CxtT{O?{5@Cc)yi!LCyB2J(7UT7 ztiHi|-tv;yG+TW45bR=Ed2I-kQR$~4TS}xuL&!IM*7A~P;d?LnzF1kA5Az)!65E*; zFjbKvW1-^BAzCWrmDG|?^pp=2H^YC`kT22zHlzHINHO0RLl_)Vd#fg5fnQ`Lm!8Ng zVQm`~8*GEmt1z0&U_Tn1qiLd+BMQh01e2a*6D99T(^jjMRR0YCoi5L~Y6w^cqBJoZ zF$){zljREO!IMDccVnY0VOO0PwaL5WJ5E#{I1&B_c8*s%w_uZ=g3)|>>En6|_V0|c z=kyfZ2GW(R@Dn{f-zzOB`$122d8G^LuR2Mqm8QUVi=J|M@3z&{|5;CZkgRLFOHcZ| z_d90H{)b31`(r2F?*Lms-_*Owz!2SR@Y)*p%1>%#^rH{^iv-y0U^Cx238D^DcqK$l zJ}<*ukLBNOV}+QB$*vEz+F{pgFmO-C=khTTY!ktD1O=C~&)`;iN^RG3-!8NWX1F1W5Myg#CbH;`IV<10Gjj(%2&{bMj3 z=38N>L6vMMVVg?hgW((>1rrVmp2-ep_>-li(s7?Tz=#U)Grhpv!%o;Q_X5La7T9}q z@EUezkz!PAZ*l?>M^=FK2(G7?MwTq7}9r?jm$%7t5)3zl29^(Xmo|7C+Q*Rpc? zU`(s5tcKl$U|ls-Q_EazcBQB5BhAi#-AE?Q_<@u z(%S9B3@`f)Ebt<$v(>n9DY>C{kJq!6Cw=>y&BK({ zC+sL@GyhqJu9u?g3wE+6$}lrjgHr#?)AOl(yAw(`p~<9GlfIpvq$k<6lP*5_J&{ix zdDk=ft{hMU$M=Y(*gr-r#e3{S^GeZ6i8W!pB<~Vhb;)}P0{UM$^SQxK4U@bFvow~n zBs+s#R`YXK^xb<~?pCYu3-*-p;*;SD!U>{~eRt;XQ-1R)yQV-|F&sXUyT)Q3W$QBb z)yG8UNNbX#cMiegjNuo1DTGuuY>!AA>r zQz0{!m|*ZyD{uVQR(|{$jf6&=Fx7%)j24rUWweior(zqV3flNQ_AEE9q4Ie0n$c= zNkv79C1X;UG4~l>$r@Hv`?BHm-dQhcS;%z0Yv^4r>z!04Xnvom56v&ZBK~cxO)lU> zzY9c9=k-+eEf}4heWU1O# zuX%F8?IOk6?j3HHq|*!T({n>!{NmxCT5kKj!!42&DcGgwwt6j-3l0|XnpoS1Cm{Fq zf<1b!$1By9{HW#DzcFyvSc#4OE??V)yNVUhFYm&S(!5?qj31Sd4gkk2GWQ^c<50;<+5p*a^^*5(7?sG!zsd4G3|R7h zk&OiHY z{}^7v@Y=mn#P+F3$stIY@&m(RgI~!?zZO6)dSq^{NNDmKbSwKl_FKt&7%{JRfLtnh zze9AR{2RF(4V$bVD>n?*g!8vpV>JI01!c>AOV5@3c^y63o?=)`!2VzYlQ*2m<>lLh zRy8X+tIQBMLS9PV?U2S|Hb^ELt;^&WhUzl;ULg}UiQthUdMQSEj8cMSec!xrBfAp? z*zH4ws<8x#UCmi?8JO_xL*Mm~TKlf&T*yQ%>BH*JAbnlWwVhj!T|+5(qS!S*H;Y~K zB`btPpd6+3muc}^xd5cd%llFSK@9G3oQa8c<+k42ZVUTJzWKL&=e_G;(UcgvlD#c| z@Y@aL%0uO^pQyC=ZB?JO&{nHqC%fo{p^`TYDjk9|fOalB&KEkwdghb7QGI3=qQFj` zQs`BjE{a0FP@J@0Uh-uFktF^}(zf!Y2DYcgyc9apM&(x9mU1i2{J)3el@PgPYx!F( zSnGo$q=O@t8lNmfxLqL9Xr&2-&*&+S*IL*1f|fE&e07A)_3shP{>%vLJ?xzKb$G)kBUt(V5sN0d z4v%1KS>X?Q@p>;Ci_Y-&Xf75_YAjqlQcHTgtgdjWo|@=gI;o}pN-Zh3qRPT+M>2Kx zjT*?%v~DCTykjK8bg~QT*XsqTje?DOs>#bH6g-rfDttsw`MhjK;lK3ML@#UkyZHr? zWOh0xqZc7YNdB&ubf7kCIMO)iSyKwIQMg zxj|cq?usk*T8OmDSL-Pvt&%@!DLKU7iC7AYWj%p@Y+<1-qlAHZ1KzgVs^j@MUU$I` z4XahhTFeS}>qzHtq>CEf){xH8kybGm3|hWt@R|#~K6X(B3|ih^2GV{5{HmM)o6>XM z-Gl=}L6vDB-Xr6C8b@b5#Nio#BS!Mh#8Lc3e*(8ZYok1nSp7L0<>F;@hIk&|`rg?H zY8K;#KW|{8Je)8>fD9+W1x}MI+c~EZyhf{?^BGC<#y83% z2W+wnH_E5Ug_8FVTDVXP7l8pyB?ff~A}Fv-1j`YOm-nFH3LXQ(N<9;us(tD{vg!@GJ zpHXne{L{xBj~k4)6z?Ck-3P|HbCk5M0y(eyq#YG6RIvAa3*(!Rozvhg#yfAuRqQ^a zw+LM!AT-wXZ2u_ZqZ}v{)sIcL;E>0cQ;Z`$cQ*3xs=%4e!d$#h@sWuRd+7g#)`LKE zS8#;=qh1FJ+3I(q&VTuGw|($U8R-?p^=vo0rT8w9CE=t; zsw>!3ED$|l=R&iY0#WruUD95334_7rd}v;S4=|2ePZ(^(Rgwr|m3vvnB!L_4&>$W6~x zeC(rR8Bps!g)4}UKs~XZ_a6iK*dbc6cx1D4fqvN$C? ztw)~KBhMp3rFUuJZg}p8!+&JjQo@>R->KL?DqE5$G3Gp;V6G*j1>ltPGXNGiBn?*J ze^aDYSZqUS1JpNaS8kY2@d$RGb)#Xb$-D;oAr#rHF*G_&43=+e=L zhDZkc#?i%X(19v2a9`1lfNnVx9&~0McmL>v*-#R`7($)}Hq`beA}+7wC_FTTm1X)X z*Rq^TPh_PjSEaA$l>0~j`%KhEZbn(YCimsh&`BsJ)C3L!i-sPu z51XXbpDlU!Bhn1Mb6@Evqwy~A*C&~ba?V|)P_ce=5j+3UHQC1XH%gL*@wJS~MJ`FS ziVyUZ$NSbVfTw@OA#qXG6)f zS??V5&FD>)FP)7dmhF|G4B7H*B^Fy&UgUvgPbGQ}jhqLTYjXDxs`6RoTfE3u0{7cW zv>+=#i(n}^b7kcU5v=BcWsL}KrT_;SX!hN+jq>g6wkqY$ssqqu;TUcyTd$?$+1`He zXfEiwU(as!?m2t1Z?m4?hGYSpuMg%tqGP~SE#V#F&DQLWi}Ci{L2)wlwFtfu!C_=u z<(u*_c7t`L{LSd?ReX4BZ9Mp-l7{CiT8^AI&&8WoJAR|=tb!|u&E^iR-9-1~oHAPX z!CYQCoG8aK)bi9&NiOA*ZEVhKgWxTSGDtb8F6Fhd2(Q&$W66Mkoqe9Pw`!?De(OBx z0EHi&$KE;*`Y2vHeWnT`oThJY)%)jB_<7Yn3QNA@J*`-5CoNT3A%bh|pCi*(H07D9 z1Lv9KtL0nl$~C!AV1;5}2PKIl`SC-47MZMV*$AUz*LAiHaD4s^)h(7ElONQJ|G~>H z>e{ZSl3uC%UF&Xd%o<%=PJ4hSLhH3A~MZ-kUMEP`c_ zFC?*iMNe67wJ|17pfP|Vgz@7sT} z|Iv@B$iYsf8Tq4w2RAyQeK;!i^@djRZuu`gG6)VIMSh@-- zAva~kK>+{3nA-x<>#a%*eYg&tmD9F3V18CLDq3# zR|3p%I38qFp4;N5DmO6v1hD z_nvkb#Zb0OkMvOO^MOg>AHYHpETlN;u15J``Oqlnt;WsP5%OEuHaMs=@;a9rsSJ;< zpeT>6L<|vV*k3vit;vF~Bq+4ovjsCTg^*^tXz8;W>SWa8pnk71;|WuZv|pTAbH znAvY}@eH|vJO+CQ8D+bk`4Bq?r|rDd#49cT7?7U1JJ?KYS)L=UsIp@ zw&1nzjashf-y@8k2V6M2de~(o{Rk(s4O>Mf^FiXtsaGG?vd0Wh>y_?y*$A5}oom@6 z##gi=_5XJ5h`>aJdfbi;&%2hX+?Ha)b6}=&TaFFSftkuJy^laZNuDj72*4r;lNt-xDBkHMqu^`0p2F_8TVj8m7%H$fG8 zo+F*QSneV#mMTA$toq$r6z0my%v(Xm?;e0A$V5hD!=i@Y5#pO1(c4NGZ>ON|>QG>O zOi*-ZKZO+eLIWH?h~(EI_yz&zN{98xcY5Rp@KJJ~f{@&Wf330r>+q|muTDOE3XP`;L@_8QVEeR;TChOBsZ*O)(1jx!-8%6+ku^Xqd zva-Pq=d-M2S^!Ygfc;8B;_lL|ETx~q-AB0hq9;&5A4bMiNkUSrZy^4Vo2#i0i+Ud; zq>`ru>Z{0pFeypjx|cenOFb~wR(lZpGd=G1-0zVs(v5Xc^Oibn*6&Quy1#b6lSE57 zi#;FmaHZ!{3N1H@FlU|T3l6^~_bVQ5_Q1OXN4n=QC6#AH@?{ToH|GNndD$(WdT7|T z{}I8WI!fiN6v4H1-=QG;qu;dD!IrUa*!yF=uv*!p?b40)TT0~BJ?zsY@+A2-_TAuT z-QEwA*wxZDY>6HzFLB8{ALPNL+OQ24@spBNZg^-6*VBILW^F0$YbnEPYrI}II|aO!6>vQb zn3)`EvGV?y%e)*}Ch1Dzjq@a&&#V$~)}*Vuk+K^#5adP{f2-w!vRk#%^}SeX*ht61E_)%8`3$mLP31{X{K`!@V40HPO49(=Kn$j^v8VQkLk>bO3eGr4|^ z@woV#s^TQ#qG#DlS~K*fn9PrrNGHBYen)GDgpZIhqR1WaGeKa}GT}|qK~aN?;Nrcf zS6GrO*~s!_w6YjK2>eKEhK>yMX0By-v4dJO_4Jl3R}4fm_pzJUw^|wGUDbnOa{cxO zb`CTxYF=)jGD2S$h2DarcTv6Q+eA{A;g`a1hgsQj0W?fpm-AtA;x+Zx_bg)T>u>CV z(#+@Xb9wf~+L-G9TEDi(csJuze|=B8H;Y?LP#y5L3=|9AG7Ljau@Iell(`<#+VyQ2 z^*|4PM?voqmhBQi;VmnBO-sQ^j7kSwRQ7>ZW(C_a>K{EAF-onM%dQ(MhS2a_#nv9Y zp*BFefhBL&fKAU;JTHK2+1>0f0xFZyB&;8r3Gq*?$*pn}KX;>E8%Y1muwXPa`f>F= zC*3>tWl@!3=KEt=$*VYo*gIAT{QFqhel4#{%PZeI_U#@{(Vw!iMW=}BLwe>lxgYnC z2`C+6lh>T`q{voU-yf@#>^eoM-*d{RJ)UD zT{4f&Mp?<5Lo%+=JLL}fV{jM=zF4G7AmCdi?}-#IMSLQDs!Q>DDEkfa9pD`JogqssstnL0L=VlB(d6p+z#?drm+9nB ze$)q8T+2%e>!nB34x0vf@XU+kA4D??MG7uN-s%$A3gn%q=Q0h3U+M6?w{dzUZ^BJeIDWnC_> zY1rHV;r&>{!4Yk6v0QWs+u1BhK-;7wxoN$ZL6yPJ-Cx7$!PS@r z%M0)j(ZggtYf<%vA{vElup*Qf<6z}yKCT^+!OA6#^uqJO5hp7v5W_*`5M?zYh29;P zK({$#QT1~au4Id8aUXymq}#PvLhA5qJjq z(tIs@&Hmre8vkTgmR)91vdXVkXfira=4z`GDZ1m_) zj{;71gYfhfO}lh7)Q~S4>1Ex<9~#-}9{Ji!89JP2y-t4Cy}lmW1GJde8@b2p-!6q# z?!!hN?BnCeAvQy&+5w=Yo%c^lJOlL zP$CDSwX)~NrQ#(oX>obxzg?^xnjYuQ53$mJYZ9;!eIXX6cTT8}ky_>BAuVuCsIxvbVB&nlhn^)^1d`uW~nRfg(e4{*Z!4sHhe2$=r ztQ33~fKx&R!Uj5|tmF}>@OCW+Kk^qjEweBkb?vv4=jm0zG5)N7L~iz$To-Tfr#&GB1G3?bPJwb{Cyj$X2#SRpvd_#G6%+p~Z^vjUFbcjcdkXyhk@8}F^+ zW~O)@`JN_JbFpkO&~T zReXF00lOMe-kXD%m35-7yD4BG4`0z2fZn$XR91ptI&V4cDPXfMFHF>}kX3`0vQm_l z*ypQ&x;tzC_%D%f^FDIgfU+k~d#T@F+03g9o8-xVHotJ%6*()+t1MSpF3WoRw8u@K zownKZ{b_F)FPnIeY3;=Q#)l^UWPE1gpG|L1{J{A6#2ZXMPdsG2s^wDCUs|p0U} z_)N<)rhm6QZTzGKrzzRbWdGE1ZT1zD7G+;=z1eyTKEARpdr{79Iooq?%iEa$M&6IM zHIp7QFEQ;hUt`)~zB2Fjs#ox302AAk|5d+3ILXLjpF6I-7+)ROmTy+@zWiA$Q%So`I`Jq>pcd;Q#91Gc;09dMv-UD21#9}fI*%s&Uc*Yru{$BCPV zJ$%k5qdysKV)vD@C?}j$Bu9d;5Cjg^{o2-ROAmq{U}l zmwW4|f9372`Z0IgsN0qMXZ&Z>>cF8P-<|ma-WISLcR=mfRQ<&z>|NJ4cr<)_7295Q z;9_<^JO>7Lx96!V*cSY>$&~j~{f3q7o~8|}*rlE?ma#*p{^crmRl_q^u~nz+T*>}) z>Q5`#!RA-+wn542Gv6G4=_+<({q_~?ry&oV`9&P&t1uu8t{8C^AGX^Cu}QU zKI`C!M`yoMysu^H$UU?7R$Y-iP_-#(_8Om?bU1JSq(A1b_}z21zy5Bo?U~=bV#4wJ zru;Yiea_fNzqPYTY^Hz1ByIzii!l z;-blSp1OSU)u-Gtd3(b>lb<{F;mQ9zW%uN(PyKN6>tnx}{QW6Qr`$jGnkgSN-!)~| zn2n0OdCE7wol|!DaBBGSly!qXm~wx?w^N=exN7R-1J_Nxwcw$t9~C?`b@2(GzI^M@ zOCEdYLEOk`oi!&mt1`wf1mzEVI(pPwL#uI9p}UTd{y(}kbf{IrJ*1ABXh`oi1dR0N zY|LT3XBT3(z?8Ns~`GuT`jGHSPLvy-uwTlnm+OC=ti$(&8 zDtvA`0|DPx6nC1YzSftKnY!glrLWW_a79Xc72S`Pfs$FCe`m33euCdUh)b!^Anwzw zjK$|v&9CB_v8qmgFj|$u(128DYEZbb<7fqOPf?G`uOyAcj+W0Wrbg43vG(a(rS{3k zO8yDNrYY0)FA#U&K2?0H5Z^KH#20AiC+Fb9tN0*cV%z4R>T&qhu)Eq{<8iy)PJbX2 zt`5}I)%u;mfM2akW7gIM9qyXyK&U3{an#ryp}HDJwKHV*_``OWtJXCq7RIL|D=X{i zCL{gQ$IvPCZPCi(>GXd`?EO_ljz2P^=G1Cd;`@a>moMv_7`}{2-@iZB#PzOPFY=4E z>j&;*L>=y~bm&4uzb~T7SS3_WNs`HQ~A-J~|q9IQd9G{FROPD5>6X zD*eA`JN=c&|HV{P@=s7=Lem`%`dy(w$W`kJRD0^sPmqEdaJp(j?qHqW9&}OP_*X9R z6-s;^k$;(2z{KYgIUrqK$Q24Y!wyF%V0U>ygP}ms9JEc;UBQ5$2x>~ZEAoh*(5GTEJ3{Gn?Jj3Xt*Z^#Rn@6d3u?7f zMGyRHZ6N5dha95zSfY{_(}-%MmOi4T)aO1kn>zxi^>IWceE>KUkJ9OGd0e&b+PYf1 z$5rF0t+xABkF&;A8}bLL>(n|&waUA7_PO-=bq|jtqEm?m)|1FNqMvD!=-mPRLeV48 z>6@GA9DV9J6F&o-?sr{{TlG6?UH;lydmXy!2?cBF!nL6~kh{CaDW;S7dL@0$;7v=pzm-}XA7UX1q)aLr+^eOfW4f2A$^HN25Ugo$m04b!{ z+)Rwv-1KDot81N(+CZJ#Rb3aXs}A|YAYgknc81+!_XKJrk)>H>QPPe1BE@LlhRqC`!fM)QfN1#j1@rm)vD^Ksi~`WiqQ&P zL9bU!9gAC*kEHJ#<14uQThsV-^)b1mj*ZX7YL}1m0rG8_lDfDeH-Y=Xj$Knw|2A`~ znA$=2As$!rd~w7LgU7_j3~HZPrf*08H`LS@p)=+jGwu)6hJ)3$A$LHnt#&|QdFuSC z%kHXHgZ>OmS53g>aMq~U^SJ)R9}e3?bzlUw-~hGuTEDI*9C;(v@n1Tx*O2+~?UZ^Y zbVf~e#8>K%$fyiN=HPal2(IvH>rT!9y*X<6%4N#>zKRu*21h&FnZatiI}_MxmDhl ztpr!-Q@0uOl=$NGFOOC%!?E+Lo%*cN9&^;{sf-5FbER>ZW7p`Y{a+TScBTq&IW;9V zPoL!wzTY0i2eZ@L!08G=SgQ`Z(-Cw+eW-=+@&&+oErVr##2zmfH0zx?qQlvw`W7->M$58`aJe7nHp%XY`vIP)v=LK zcN*bX#MgVUsSnI+wv+a?^-3`n>G`dJeg-2YT;raf#Q=Wse50k<3S-CkQ)?XFX6f^Mfhjp=gN*sC3$Iv2<*6mZwn zI72lK)gK7ggzT!z<31X}^lm!iO7Y$qmDFv)^lDuhT<-|1He4+ZnS$@-|I%FjHw0?u ze@80Qxg&s+EZ3^j#4B>p6Cv>{hs~T0I~O)qTP(W30e8tTeIa0s^!TcUc||Ox+Sw~d zl+UP$7-S?I!99m+sn8L|o*XL0O-AAZ!m%eOD8cU}qZ%T?HFjt}b%6|uhgA-f zrOsaM4>@YXVP~+qt~ThYafD#QhgE;=aq%88lSlBKUn8{XE&_iizd4+*Q;IVY8}X6y zf2=(;p6*jW+Jy!}YSYRIyXS1zxrmF4cT4}Di!hbk>>tT7^i-+?X?oM}c#htqKDocH zi>YdOUn|uMDv#)-PI0)`H>j!?q}MtGW7Gq4DHyI%>%gk)_Byx6RUNFhyB$z^>%`!X zs7)W_$Rpr-D_wqEFg%?U(#D{_E2FVsUBK?F^LQLCNU5N|8nUd~?}2geg2Lws{IVF; zRH(FUJ`U8>q#ScjAr$znBDfU-H^2M>D(DK=Z&c!LNAB>7Pp359L#=UvW6@QHMyy>_ z@(Yb{b_a=>*&p6e#sqr&wSGsi+8J>8t6dIHO|1*=keWci?{K^8LK!X$u-X>+6Q8aI!%a=o+i~So2#x9u&I$gWTlHF#DX(vJ zsM_I%A`ymr2Ijimt$M26ff^)(P7gd@Y0?Hs*ZST5YEQ5Zv>dK;RtFv7K(MZ+CgcGp z3JDrIyE@Y>N~QFrNdu0xBh!PPFdt$O7qOwdF2<9=u3_XaI0iR3a--_CkQ*|iCfp{U z(g8R}*y%b|whQK_k|3;9*M%N}qBYJx$JA1=uZ<2UTV z{;yUYVLMD-XD!_1H4e8U;D_R06RxeTJLW3(oq$T*!$@}!LG>Uv`bf+MF7<(Q6UigC z7tIeJ?D5Sqcs0q^jO(?t#+W zYZdzJ@u|&lP4P6${$4)TnDC3Ib~Y)KEzTAyRz6bmX^jv#z6_%bF8qux!$T6VJ8B&P zbk!Mh!gz6jPVBXT8js%<&d}$e0=PheAy2il8vazjtF9J)cM{f4_}pv!$DCeT2iyP| zj?7mx8h1gu*Et~Voner3t-U(n^i*T2UDa^eID;883u7u!UE}b;zRnU(Uf?Ai;T^$bC-Ju$f)9!cIhShNDoa#ssJ117v zHFr)`M=Vs8(b?3H8hRX+j!I`;RR_PY{%9yzdVX^jjha(U22$Ta_a3s&D*taqQoYUW z%JC{(sSfG87P^<%kVg(C-3%QJrMXQSuFb@ZPF!uB;itx((lx3J=Y^m$aL1quuXV@~ z4A^nKo?FdSl-!<>>c(l96NfKBoHf=~2Z!6t=;e7Lx)48RcfqV25_ez|)x6&P}8G;t4g57h5WdP1TIdN`jm|rkhnv9ha zX_TU~rE%O@9}kTwJ-KJte{YD(p;nynr*(rP>cthfx)ZcCh}q&5>++o#wNG>TW;E7! z2FCZXJA1X*b8hL3t~t1p91~fZY*$NBv5?LnhElpA8pEMc(x5WCih{pW)h@jl?gZ_L z#JUp|rHFCWBUH+TV60b-ed?frmBRm!=*CUWxN%(#l?E1+`l%LPA0N5quE?C#z5`7g zLPT2Ri{bY>_Ubg&E!CioP#Q^;cE*yW{A$SF{mVcPk-OxWm|Vj}_o)HhzjAWuzxKJ$ zMV0yr(-sPKXlE$tnXgPKeOM_tal}?g`Fzs0@wl`o2}3@+0uIRLkPD}15V#@8zgl;- z3r;z(p){sFV6UkTL#eh0>VkBB?1736vppE{_^WX`cT7@C>wPJ0{|L8B=1GUPhpV&@ z2H!6kjYpSgLvgxH@5~HMreB(A=&@J2DxLK14E>=_z4nRz#B}(!Bbo1=`~nMTqW#Vp zSf<&Q=61hebfyueP2;7{cJy-O2$=IIu{q~4dRoD+=&{86%b(=c41iL`jVLEoEm;<0)E{Y112 z6#8FZt-?9&FZzWz1Eo{rUU$Q0EP5XAqK_W@+`pFT@{L<@Q)eTSRco4rsn$KMCzI3}b|ncg#$&Q9x{ zeTUGOS~N}lIQGp{xD<}L0;KyL^A$+iS=!qKZGL`*^D*m7WAp8JEZod(_+R^(u2(R3 zaeAHFxHt}jU5J+(U3IPid@;_DA8y)^#}x?RjRbebn~mXGH(q0bDPE`AL-s(h20rgv zdIQg2U5A4fVQZZ25l3J;F{IMRxlX!0k+yA*RWP1;@pYQTI6Yz23GVCmI|6V6I9zHt zo^aK>z>b zM;Il)#+$ee_ir7uSAF{Q|FZWVa9S1T`#*l31$5c95PNsU3R1SSi%~&9#RdxYf?E*< z7FkfR#U5if_LkTZY_aziHE7frqlqTgB*w%VOVkt-%kO*GVe35 zLqqzay2pR~9DCY<&4|GoNyW$XdMt2bbRMU=6PNTm^22%YHmmB8j)mP-OhKFHg6@ua z!>ZhLxSn>j2%4v&uGt-l;6_hf@3u*>YS}z`#h(XaFF~2@YNquzG&eW5HrKXxwzf33 zvH+>6v$>NN+ZdY%ViK*TqrJYNjW=60bv4aQoMBGFwl~++)VJ3cH4B3mSNvIfe*G5U zUlQn69Q6V{j1zgkuGhxB%7w3JiZ_R3zHGA#?|r^SX<|8gsn?eXeGI+pYx5P|?@Asx zE2B}c$S!%e$z*PQt4O`R zyb^BgWN~_HD-XhqZ`<3oOq<0fvC__#f=@e{Z`X=qPg0Z3By~#hXmtte^;&s%ZE9@M z^pff1RLHD~`9U(eMYYaV*4~s219bS2GGBja07|j>XCm`Fq*ZM5{6n|>%4NjVuHEM_ zxf7Lwh9(Wwt*rBHXs&N!wxNmTw|W-oXl`rQYbf^xMtoQwPk5vfkFAJ|beY;U#UvV+ zc51fo#BkANH|Mes8RoQztfGp``J2t?!QRrlb0dx(D-ypqbAWcth90q6(gA8yF>G)s#A8 zXY$>Ed(x53Y{J%JKD*f<@tm58i5}w!CDY9~Z!{l$Cp4n9rMttdJ?%xVn@@dO=I%Uy z<~-9BXT)CaGk+dt?p3;HN=nNs7FMM`g_fp$4H>B(g)DwA<~?=`3Au07 z%tHIaIvDOn?UH3qb&U*t+j+T1TOL)zb4_(~eQoV1hJDqeSo3Q?F-zBt5+*@dPtZQI zV=e_US%ro?w5Q&}M15nowoY^*_b zOBDtV<4O#?d3^4gTXpzBd<0b`>tw|b8=St`M)RpW%A5CIMeoGp6D1y%b2CezSsc~C z$0M-mQl16TzmJj#amI2@rDLWXDI4@vJVisft152P-Kqms4%KO+`5wsQ#$xGmiZ*U zh;6EG;eAhA6K{y_z>sdWf-Pp#e&fX?s zd<||-FO6wzXlUTmnz|;w)@^KRYh{%I`7%sjDs+e6!nybI?Qim6<@;;{p!o;w)c z#)LWG;hhCPjZF<5jaunm-&RvwH;TuOrsnqc`nHzXR9?%dh7P9NnPll?)~lK4v+AZ! zmI~F^FhJ(}(wNr8$~%=;-)%&dCCYM`;dsT4F8dtoi8}gdC8kCss+js(G0n?Yo+)Wv z)}SK>cc@>FPtZ)B+jZCso;P+O$5pfDch9S8?WkgQCN2{`0P);eRrH;0LNXCsR9q|# z>KIj9qjf|qie*(%Q!SG(e2v)2$GT1NPhWbN^Dy@&-($p#RZP58)3M%*JeX-Zvp90@ zki4(5`8cF`R7-t*XHy4Ltt{DXujX}Fb!$B{P7Spz|#Ml%-*b+-DyVmypGxC9iGFWhZ?7IlsH3>hO+D znfcXfLyaWO3YeBE`UK+AQkL2J^sH<4u&Ry)Gx)N1dezLXt~v28h>>}f>AM({cFayj zwEOtURWpqWcJ{e=P4@RpZ=DgHRH2$j@9y;T3M}QG!xvbwEKbmuBho6Ie#WXUNv~`b zEq3(Ck(&K3mOg!#Rlv?1M|Ux5MhlA-o2&Uqix1Yg_jAUynm(g$XSO1eY;0y7Nk?5X z%N^^P)^6x*Q=Pk%?;wJW-yyHOVx_aX}Vjcci6|g=oTJzaH^)rXkpjPmKn1X zPjkI=a@51{Gu`mkTtBL{g_X2CM6oEAmx*5ME9)l_2&4#MCjO8{O*u>ham+Cqsx?H^JW)juX9KX>=*dn9>lGb!FAwG2z;d=O9+G~!YK0aS{mc0Ba_-TjF;7#P=sVC-agGSV(>&vvL+FrQS z(n#E+$Z>Cx>#a_#Pl<6L!nSdCg==qep>?7h*LBvn){SB#iiVat-gtG6YUCDPS65?ZRXghWC?@;% z!|ymZf+r>Sfnl+f<7>CV4-q2`MzK7D@7C*Bmci2$+s(DrwpBM_@#fkl{>MhYwQO_6 zr_N3K+>TW;HH?*c#_FhN#c55g`GBVcGmOhF-!Ep}8QtS&khzj9>|`S=7mJu_?WtYc z@OHPPs6*)}a#!XW6c?YpVi(1hIUO!3HyK2waz{0_O(W`AyV{N|YwH@YcxOAG9@jK> zG&OYcvcI944?M9u`v%n1G0@{BT}wx66FbsT26b%6*2rgO;RrQ~x1pwSL`x%O+QCzD z-Kfs?mSz@FG*)+ZcJg5l56$dPzzPGlXJREzO($)ou7S_^*tLREW-_#+v!*H0TJ87g z+`LLMSd{6=V#SJsE_l|QX^lR`>0PFWC|0dU*dWFGdMi_WNs*PQDGtagondc7M681)1a_$T?f-tOkglU!ID8HKZ~ZD zm?A*Yp0=8?m8pBAw&!9-F{}pNj9Ry`+>jC5X_2>E!w{pjJfOP=%jjc6xd_KuM`Y7%d_3h294yvtZDP2@Qb^ESwZ)~rl(eouKxAuDW9?>R+ zJUKSh*2ZdGW@c|<_XOtuVpW*^7wgznl6@s>7{$)X~yCFS2MY zU*vZ3#+&7#)t!8A%Zip(#!r0o+R?x`l{>Y42OfWBozXRt=%#nMIJccYGu-GeMumz* ziLr`^?=b5-Tk6_cTiffJ+nd<}fEz*!d$={TN;;BcE}*TYy|KEwmepn2f|AVxI@n)E zuWYNEM(Nok`lNc&95aDG%uMHT#Wt1^gEdXq)#)b%8M__cHTRI%l-6u=XuPMbo8#91s*2+#G zM*6VFVk0X9*@%>;QBz;X8kbR&RcpO^Wfr;k+e^6ZZWNSVr&{r1Yy@IR&=p` z8*B1yO-Fl8tU_gnCd2Rc3pY_{C^W(R?x(O79 zQ9WXn!lz_}GWPLT&7=NY8K-l`^!Y4gH8rAmL&}cAS@vUY;=YNP@z>#VrR+4tVws_B+Z7A7vP3Noc>xwq8aagZJNmH9jlVu*$ zR3Ex0OjlYMJ9pr%G{f!>Ra(&0N~Eq4Z9F`%0k#HtOg7KPTt%(md(WZavT&1bVoA6; zG4#HU;InfKHpE(1g0#Qq~MkeKtHjyURW- zL^lEdXqCH*rxaxCBAcY98L0T-Gl`j)5#bAH_6#;f<`t6JYBT<(Db9OgK2A1rb+s|c zi1cF9+gfL^Yg)D4DtUy;jW_cxBZiukR3q33X1G-_P8lXnq$8{~60r!ArRAbbk~T=k zp?Z~;-o-2vU2-S3;i>dnVh@so?GiH@rj0oHoogoILQM^@`1 zi_D(-F4>zY`L%MaTXhw0bkz*j4^MBIS>=X5v%6*w*A9?XuA>FuLkl)H`En>RmnWcjCu1~x)0Iv^X?Jh zsafn4{v1QZhbx%vCcDuE?Qa-{Hr+;erlu2~`0Rv4tT*mqZAYGHyu&xQ#+dH07^A); zjKqvcW75&bm!U{3f_OT{ChcTXKH0Au@ootiFYc$k41>TwXNV*n;Fx zj=nQIE;@}DlTv5K@=e_pie=Y#p{7K~yL-NAN9-`D4zl=+l%4Tl3(`{FwkGdd#or2O z5gLnmi>dCWZAwwF`0I3=3EKy=R-9MWC4Kp6gBAa1&qZBrELt=(N#pqb)Za2{8guR= zZjX~#FYiAt;?qDj+3I36LUZJ$JL9+!>CRET@n%Ddgc!n@RJU8#3tIW2dB~>f<1mST z{TN2fn59RSIsA%C(OR9(>=cucG&|HNKevK*B;fYtjVs{4iwJMjKApz; zoa*0b9~0scxDU8}vhFmVYVm3lJsd=1f~YhZyXIG+SSvziSe3Kn5WCnZUa@+4rpXCy zlrN>_^b|H`9~(^~Lqn=IbK0_OOUp`!^iADt_`WFIL+IivqiDPN6(!zU`F*MTsP!K2hL9|+a1KMlf4?H+QYS2%1lP)C)!JA;)e4%Qc>!?$V6Jie(WJt5`HX_uGzM( zM@hxb)6s_ey7iLJWW|qLIGvBAWTZq1a*=X}YkOk5xr9wCx@0r$YfM}d&qZk_m?#Qc zqtZDJcjixD=+Q=g0&nsm=^*~fl+2abo;2w|{7iuT&48Q>=Jx{}?P5(OiMqTz7YOmM*4!C$?WB_)-SQnH61qo z=}P*V*HXi>^tEvPN&14VR8dxKDGhTEGjtTuB_F@6umr;&WB7Q)MT;>nv8a0YGB8|9 zNyVx{dMxNtU~uTBTWq0D(SG~ifYEv|Rest%2%Bz*cdgO>^}Z1MYS$hTSen}OZhF;A z{jTM(s{9)2O2LZsd93crH(t-8$upa34_3&US(?OzQ}I~U`dS1GpSotia+SrFe_$Jlp z-Owd8g;2r&>mDkl$p2M;l&41ThVK0~tTBZ8lp+1>Oa55iPItNC zPj)nt*9xK_J=C$DqlvQjbt6gU&5We;GuV4;&l}{%t7#9pT!QwTmr`Vs{|D(rR`b7@ zkZ-gS?nRME@taX<09(9y#0^yPcr$Z{|K^j-+EK&Vd|O6MJu;b&vaQ6m!k!*NViCov z$~T``W8eR)#3M_)Rg1P|Ms#AHYY9iG;XUe`s-53t0+A{F7uu#viRft3rk5w+P|I(w zMW>R8I={H(zfP6TOCT~u_yZC7wW#-dMSMi|&D&(dHBM zz-Dikd;|Gjp!m41XQ?Pdy~spLnJI3w^LcTV(j%aF>G;IbGFf$UC3-Nv?ZEA*-^3It zy$cf@DQYS|1<*C$(b*M2ZW%yr_Px8`uHR+f&T2Q>UtNzbI zQaa2&2Y$go@v@G$+jz}f^4Oz7`G(?-j5glx3YKr9uqP?e5~avTc`I2u($*DKT3cHI zv+q`R;dgrcyFgoMYD=gzzWt|D>9OAU0N+^E>01Jkg-fhGOAvijB-ScJSW!A}gxN;rK4OjS{$=Y)Jk4w` z;7x2k#tF{#)-w>Lk6;A zO`m2|;s2ke7@02mv5x#IR#H{!d)b)+>wB=Y-bE&_!dbGC%RCJ6HA->GOkBFEn#00EqrjoX5GxRLpl~_B*O)ZBYeDO1!JD9 zuluY!v9fbre>Tm2bdZkQ%QMPK$8qK(Q9XLMwOT+?3sVZ=bK8>II^JrQ+`?H+`fBTMRFB?m9ojLx*@VoJS~iTJrzrbs zqS7MlJ+IO9lh@2-G5)X^FO8ntX%7+gHDAS|={+}L@54CGEvwY!vbK&Z!zvxBvExOZjaugh-J379K;4WeO>vL(W1l|-zBk8?@_0!WpziY zTC!Ka`jB+XW`mYAWp=_!lToeV&Yc`c^;T;#0hXlJ6p5tNTGLw;J=dB{$gfdr^5Tj$ z=ETi0J35L?BaI;Cr);BDHB}DtsGM6N7XJV zmQFT3U6hm_Ruy(($*TJ`$}ycn(UQre8k7DyCH{>nk*XWjd3D2l?2s-Dbv3yc*wbxp zX19XsoXraC4j{1x$-u|_8iw26DLhSAmwnyOXjzqp`cot3Pkg?JB>l3~Q<-S}&vW^B z{dq=ke>V#Bg8tlT;I3wZ)p|J}E+o9R{!7KoSBm?(pT~?snvG~ONw^JayqG&pi+RhY zes&~mKXwdM<+s`DK|EHyv)z=BB9-*E>HElTY~X1&%3&w5Lq-%I5u_SI(e&K_t@OyV zH~OOSCmVa|$z;3lB~3rr<*yMG4mWd^H56VbPF!qSBBl+P`4YE9tc}K<3>h&&n~Wr?kOUIj$=YTV z+v>;eU6!HEYxigmv!1S-owza=d7|jjew-uSMrS<>Xv?nbRb|6}%p6ly+oWZLW98e! z7HGwSWcr)<7Gl+FdOZhAq)DT(r|$=921I zhNP=EyGdp)n5BvexTChAnpw8mT|A>uDwO_rs3Y6kFi> zEn4;p9Le3z)qqH^(&7wjMf9__c8l#EexN5`vurHyiXD}O0yYGURLR_=Dr%yYm zW9A&TLS727&13)Lr4Y>#OEZT@kj15;lUmHuu#qgzIN1F9<5JLT{v7?TX)L1n4(e*u zBai5s-C_4c%x#`Vrn74|0J(6EunZ*WuQOhhUH_dii z#l;siR5y>*pB}_aSJ^sa`bZX2_9nt6-o1&mu*XZhyNYKZ?3G7$Ea>SM*&>)EbrAx@I5Nn_6?`Qs%u0VSyrXaX%kPG(B2L*Hc|tCiBkDvs&gR`t)2mHr?() zVOL>H$<8@s#)6Uh(}Qd{xM}I0T{BXDdJyEkIC4f$rJ{@T=gt&*2p-pIZdcd5`0uXe zlwVw&wMH{K7nTsO78*^P!}el5$aXKB-8ORW;j`w?W1N=XhKxut0p+w^-=F4(*JXao z^pN>IS&9lo>hoyH#_u5-vv7HK%gmA83_E6abk3YH{UB;j?4*Z)M*qWG_+@~(T|Mex zgixTphe&KucXzcNQdD4W%i*QUP%7O$R>AItG>e(!)%v~E3@N8%HJ?&6T2oSdm{_c9 zjf$(6RK1P_h}mRGyL(%Z++9q$#v+V2bm_(vWG^P}Y%gkrup-Ur!omp0e(o@**=59w zH3e@IbBixEVjsxqQDUKI^ip=Fh0{a1cj!!*PSyKT_4M{CR&kRlsGz#$#H&ZVG;?M9 zq>PKdXdm2b0e5weOpnOoT5?%GDAS8xNVU(*%`x7drl$6$WArpG9Z7R78&GWx(z zzbwr#XMStX+VC{f+ucS@P3A4ylw$3%sGNB}-96HJJ*cI)m?`e1Z99ofYD?Vh@E|p9 zZoJ^qF^%tPn?H+Pdb>yN$&c}jjLk;)NzmAgLhLY>&2*IUr@ZXkz?j3TdFrelURzyT z%@60)@r!2soX_xTemAG1xtZUcYiJ&6rUSwU{E^{NEYWx!c0&iv<%~2FI#DG{>(;mI zvaDimM>}h^V+p5YTuQlRoA{C1D!ymczkWi|tjx&ha2h`e!f;EI5(9!Dm<+C+{I6H{ z`(nodD{#C!SbYA}nt{Ll;Vmn2+y-`Bb?S9fH~P_k`f)rLY`XQuZ#>xc;D`M=o(~4x zdEJPs_xw+%t}g@w2L;RjahdxTF3a&z;Odp09De6tKX`Nlj!yt59x(iz4Sw~?{_Apl z4VXS==8QjIv~cBBIDQ1&x6w8e_Nc9Ve;JNn0P8$+=JosEyW%keIerDa{{FRh%`4yS zpPO?0S1@qI)prg*x@FVB99I^Cps?3lBdydAwgydAy0d_H~tyj?aRKH0_K*k0EOB>zPIOq9Qa zjepHa*p~B~F~Uap+YztyI(E+>sKP&lbNR~F8KH0?}%THoUSi(yVZMVe4Sr{FMVEj=SS`l1gI1Yy~FYg@nx^4Nlf}Z%DL?O zrn_E#XAo@0`L6h6Jy?j3NrG$fH^l#q^FPMlnDbTdvi@R(6yIphC4YePPjc69bm!0E zS8@G6`0L}Zakoux6u#o0hL7okL+fq4>+*7X%~lYw)GdG<;n@ z#hu@XFTGyC-xdGg?tJ_Etv(0iOW!l_RWBaEm%i`dZ;ij|13^%aKNY_L{}g=5J%}&4 zcir{X9<+MwfG@ol;46QZ<11ee;IE7S7kpj+3SZ^C(L+|>ZSYkto%oVJ17C8tA>Exy9f!q`;#U^eGd@z3Ra3jRHu&&RKP*rs=^^DnKl zedGsZayPCof}er^8NT9shrkQ)4{QvABk)@v;SWx2QTBhJoc@b{#}&i|K{Z%FM!pXJ`bUc##rD1G2uDGJ*-gVsO_y-QM+{$H^R2nk|T5g<6LvlN;V!5xZ zn9=2B$L;UJN|!CyuyQ|hVKG5)g5$PxTn*fFYg)N6F0ACr*0S8kPEIzs+R3%VxCSS8 znB%1L_N&{l(U&VB;H&cNPkXd)-oj^0qFS7B{w^CDpEGI*w*xoVeRud@nh!7Qf8p?PAA` z!e6_|#`mJ*Wcy7}Txne4Y%2Rb^Iglm5wqXqEiE_5$;k%4b=(s!Z;EgF)>f{=h1~+~ zsxg)u%ds5M2lsJe@@5$NL;Qa^?lc!x@pX=~a&NnICHJQZmV3w9MQKdj-g09&mQxy+ zOthThkyF@D9Cv;!jm>tna-GW>G1H%2va{s|Tgq@>IZpkZ+(uwe7xpg0*uLV|G*2^?_(66xK*7l6l1W!mD|~ltAg8il9luMP`dX{ww%gZPJP2lbgYU` z{feCWv7;P!k6~Tkw`I@%bFNO7u@$D2x4Hh#Q>a4O%&t8`R0W{f;(o0Tdbd}>48^(?qZvMVb%Er)4 zj8om%ax)#4jh|{5dq3Zldy3@dI8Hk6Y3g4jr~AkE*ONdpzUNLiT&N4TonUWMPa-*0 z`F+n!%1P&HC%3&W;iTW?rc;X2ReZ;);RKNl6!r~u(&Xa~c{pY3i7RzfHl|%3PVtSo zN=K2c6yKqi8#7*)aLVtbu{tUn@53s;Yi?_}gk9b>$|mLR*wMx{R~~f60{0wWWw2sKuH1~X zJ>A!^Q++`Ep2Jp{XI3w2Z>8&Ox`ic7_v`5Vb;6o1Y6*2NHEdoP zl^Qm$jH-Rbz%N$5dFf6nF>GEt*Zsh|p3|)e_V+^gR<1JGBg2&kXJoj%x;D_Y#XdJa|~Y8rduBPXFu_l%S)poILHSK=$feuE2o_ZR~bB# zkt+{cGF&VmQTq6nc3WNA@i9dbI-g(jIN(^zm78Z$FKD^E{0G4g^q>-QByfKoQ%5&j;nPCvji|8hZctJh`F7Skr+3~ir9DgL$Cvl4TWY!a zcfHcb=QrCv%7X{I!l7KgO{+3?;khv;>eKM=)uMFEgZDFGV*$f-^XJXhDM`Qlu!Bnq zTM>Mck*f^nUUkWrW%d~?FCY2y3@c~SD0LpLBFL6`WsuhfPtT;0r(gcFQ1;oX(yWv5 zQFQbIF&ycTQUO&D{PJA0@xcvAw z$jDU&Yh}1n$Nc&I!O)!eHpy^#`V9>Jleu?SgzpCxg)I;I#7tW)4{ptH`Dr|y(WN5z zD5u{&CKF$IuuO)_PopX)jqJUrGG~sgJjjlR^3tdX>NB>g3=YnuTONFz;qv2KoROjC+rpKez%SOevg3`)U`!@#d2nQg%hxY2zST2f%Y(x+Tz-6cZ7Xj~Gq0rhDub&s za;3i$$i6GIaT>==UnUu!Ue8~a;?{+GA;ql^x7@PHu*%1FjvK?V zoL)oEbe!xar~3btf|wN9Bce<>6F^R+F*ee0>o&zB0+Fd{0@sG%xqc3yNEAL-6coIw~)yY~%)mPaLOw%E`{F4$)C$d&y69T->IongS~?cpIoZ z7VT-nDjqqdyP{Dd%7^M?t>d{c0VR7beKV$p(8l&ZnVr`P3Msyx`MN7OKoi zdBK>6bLH&~RZQaZwvt^2xUge%2`4q$zpJD2!p`z=;x2aFSi{&gd|Pgn{vg22dGp6NSZQ;5Dteh!Q4i$FT?v_)3E2pqi9p}@K zE;Dx1)yOU*IXlkD`8p)q-{Lr*52bs}Ui=}fuFL7#?xrG_7i3F0)q^7)=i9V&`gjLj zEiahlIobkD*~M}yLpgDaje*Mx(o0U&XUunWRMyXU9H|cd#I(z@e#YZ`Ww5`Kqgyp@ zJN|y)91*N|lbv`l)Y_Q@=XarQz$9>f!2+^L>-TzOBj{3%ey}hdZuD zmvHJ!{#&J^vVLkSa^pdrlWQ`Jt!tAH)JardsyLf@Ab(PJ?lKKBY3I|eT*A(Wnu-$H zS!H>-l?&@+72Lg!iI`GYIqx~S6&vBQMX9h#W5G83AwHjmlKj

(dIbiT~2*KjUph8DtS1S+qq6o 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 f_z|Ucaj?98 ::= the cryptographic construction to use with this + // certificate. + // For X25519-XSalsa20Poly1305, must be 0x00 0x01. + // For X25519-XChacha20Poly1305, must be 0x00 0x02. + EsVersion CryptoConstruction + + // Signature - a 64-byte signature of ( + // ) using the Ed25519 algorithm and the + // provider secret key. Ed25519 must be used in this version of the + // protocol. + Signature [ed25519.SignatureSize]byte + + // ResolverPk - the resolver's short-term public key, which is 32 bytes when using X25519. + // This key is used to encrypt/decrypt DNS queries + ResolverPk [keySize]byte + + // ResolverSk - the resolver's short-term private key, which is 32 bytes when using X25519. + // Note that it's only used in the server implementation and never serialized/deserialized. + // This key is used to encrypt/decrypt DNS queries + ResolverSk [keySize]byte + + // ClientMagic - the first 8 bytes of a client query that is to be built + // using the information from this certificate. It may be a truncated + // public key. Two valid certificates cannot share the same . + ClientMagic [clientMagicSize]byte + + // NotAfter - the date the certificate is valid from, as a big-endian + // 4-byte unsigned Unix timestamp. + NotBefore uint32 + + // NotAfter - the date the certificate is valid until (inclusive), as a + // big-endian 4-byte unsigned Unix timestamp. + NotAfter uint32 +} + +// Serialize - serializes the cert to bytes +// ::= +// +// +// Certificates made of these information, without extensions, are 116 bytes +// long. With the addition of the cert-magic, es-version and +// protocol-minor-version, the record is 124 bytes long. +func (c *Cert) Serialize() ([]byte, error) { + // validate + if c.EsVersion == UndefinedConstruction { + return nil, ErrEsVersion + } + + if !c.VerifyDate() { + return nil, ErrInvalidDate + } + + // start serializing + b := make([]byte, 124) + + // + copy(b[:4], certMagic[:]) + // + binary.BigEndian.PutUint16(b[4:6], uint16(c.EsVersion)) + // - always 0x00 0x00 + copy(b[6:8], []byte{0, 0}) + // + copy(b[8:72], c.Signature[:ed25519.SignatureSize]) + // signed: ( ) + c.writeSigned(b[72:]) + + // done + return b, nil +} + +// Deserialize - deserializes certificate from a byte array +// ::= +// +// +func (c *Cert) Deserialize(b []byte) error { + if len(b) < 124 { + return ErrCertTooShort + } + + // + if !bytes.Equal(b[:4], certMagic[:4]) { + return ErrCertMagic + } + + // + switch esVersion := binary.BigEndian.Uint16(b[4:6]); esVersion { + case uint16(XSalsa20Poly1305): + c.EsVersion = XSalsa20Poly1305 + case uint16(XChacha20Poly1305): + c.EsVersion = XChacha20Poly1305 + default: + return ErrEsVersion + } + + // Ignore 6:8, + // + copy(c.Signature[:], b[8:72]) + // + copy(c.ResolverPk[:], b[72:104]) + // + copy(c.ClientMagic[:], b[104:112]) + // + c.Serial = binary.BigEndian.Uint32(b[112:116]) + // + c.NotBefore = binary.BigEndian.Uint32(b[116:120]) + c.NotAfter = binary.BigEndian.Uint32(b[120:124]) + + // Deserialized with no issues + return nil +} + +// VerifyDate - checks that cert is valid at this moment +func (c *Cert) VerifyDate() bool { + if c.NotBefore >= c.NotAfter { + return false + } + now := uint32(time.Now().Unix()) + if now > c.NotAfter || now < c.NotBefore { + return false + } + return true +} + +// VerifySignature - checks if the cert is properly signed with the specified signature +func (c *Cert) VerifySignature(publicKey ed25519.PublicKey) bool { + b := make([]byte, 52) + c.writeSigned(b) + return ed25519.Verify(publicKey, b, c.Signature[:]) +} + +// Sign - creates cert.Signature +func (c *Cert) Sign(privateKey ed25519.PrivateKey) { + b := make([]byte, 52) + c.writeSigned(b) + signature := ed25519.Sign(privateKey, b) + copy(c.Signature[:64], signature[:64]) +} + +// String - Cert's string representation +func (c *Cert) String() string { + return fmt.Sprintf("Certificate Serial=%d NotBefore=%s NotAfter=%s EsVersion=%s", + c.Serial, time.Unix(int64(c.NotBefore), 0).String(), + time.Unix(int64(c.NotAfter), 0).String(), c.EsVersion.String()) +} + +// writeSigned - writes ( ) +func (c *Cert) writeSigned(dst []byte) { + // + copy(dst[:32], c.ResolverPk[:keySize]) + // + copy(dst[32:40], c.ClientMagic[:clientMagicSize]) + // + binary.BigEndian.PutUint32(dst[40:44], c.Serial) + // + binary.BigEndian.PutUint32(dst[44:48], c.NotBefore) + // + binary.BigEndian.PutUint32(dst[48:52], c.NotAfter) +} diff --git a/cert_test.go b/cert_test.go new file mode 100644 index 0000000..edbc6f7 --- /dev/null +++ b/cert_test.go @@ -0,0 +1,78 @@ +package dnscrypt + +import ( + "bytes" + "crypto/ed25519" + "crypto/rand" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestCertSerialize(t *testing.T) { + cert, publicKey, _ := generateValidCert(t) + + // not empty anymore + assert.False(t, bytes.Equal(cert.Signature[:], make([]byte, 64))) + + // verify the signature + assert.True(t, cert.VerifySignature(publicKey)) + + // serialize + b, err := cert.Serialize() + assert.Nil(t, err) + assert.Equal(t, 124, len(b)) + + // check that we can deserialize it + cert2 := Cert{} + err = cert2.Deserialize(b) + assert.Nil(t, err) + assert.Equal(t, cert.Serial, cert2.Serial) + assert.Equal(t, cert.NotBefore, cert2.NotBefore) + assert.Equal(t, cert.NotAfter, cert2.NotAfter) + assert.Equal(t, cert.EsVersion, cert2.EsVersion) + assert.True(t, bytes.Equal(cert.ClientMagic[:], cert2.ClientMagic[:])) + assert.True(t, bytes.Equal(cert.ResolverPk[:], cert2.ResolverPk[:])) + assert.True(t, bytes.Equal(cert.Signature[:], cert2.Signature[:])) +} + +func TestCertDeserialize(t *testing.T) { + // dig -t txt 2.dnscrypt-cert.opendns.com. -p 443 @208.67.220.220 + b, err := unpackTxtString("DNSC\\000\\001\\000\\000\\200\\226E:H\\156\\203%\\134\\218\\127]\\168\\239\\027u\\011$\\191\\008\\239\\176F\\133\\017\\171\\161\\219\\154\\142i\\164\\010\\239\\017f\\168dS\\210f\\197\\194\\169\\171w\\2499\\1891\\155<\\130\\218@/\\155\\023v\\153#d\\024\\004\\136\\180\\228K5\\233d\\180\\144\\189\\218\\186\\232%\\162K\\004\\021\\160\\139\\225\\157}\\219\\135\\163<\\215~\\223\\142/qc78aWoo]\\221\\184`]\\221\\184`_\\190\\235\\224") + assert.Nil(t, err) + + cert := &Cert{} + err = cert.Deserialize(b) + assert.Nil(t, err) + assert.Equal(t, uint32(1574811744), cert.Serial) + assert.Equal(t, XSalsa20Poly1305, cert.EsVersion) + assert.Equal(t, uint32(1574811744), cert.NotBefore) + assert.Equal(t, uint32(1606347744), cert.NotAfter) +} + +func generateValidCert(t *testing.T) (*Cert, ed25519.PublicKey, ed25519.PrivateKey) { + cert := &Cert{ + Serial: 1, + NotAfter: uint32(time.Now().Add(1 * time.Hour).Unix()), + NotBefore: uint32(time.Now().Add(-1 * time.Hour).Unix()), + EsVersion: XChacha20Poly1305, + } + + // generate short-term resolver private key + resolverSk, resolverPk := generateRandomKeyPair() + copy(cert.ResolverPk[:], resolverPk[:]) + copy(cert.ResolverSk[:], resolverSk[:]) + + // empty at first + assert.True(t, bytes.Equal(cert.Signature[:], make([]byte, 64))) + + // generate private key + publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader) + assert.Nil(t, err) + + // sign the data + cert.Sign(privateKey) + + return cert, publicKey, privateKey +} diff --git a/client.go b/client.go new file mode 100644 index 0000000..9ca3925 --- /dev/null +++ b/client.go @@ -0,0 +1,286 @@ +package dnscrypt + +import ( + "crypto/ed25519" + "encoding/binary" + "net" + "strings" + "time" + + "github.com/AdguardTeam/golibs/log" + "github.com/ameshkov/dnsstamps" + "github.com/miekg/dns" +) + +// Client - DNSCrypt resolver client +type Client struct { + Net string // protocol (can be "udp" or "tcp", by default - "udp") + Timeout time.Duration // read/write timeout +} + +// ResolverInfo contains DNSCrypt resolver information necessary for decryption/encryption +type ResolverInfo struct { + SecretKey [keySize]byte // Client short-term secret key + PublicKey [keySize]byte // Client short-term public key + + ServerPublicKey ed25519.PublicKey // Resolver public key (this key is used to validate cert signature) + ServerAddress string // Server IP address + ProviderName string // Provider name + + ResolverCert *Cert // Certificate info (obtained with the first unencrypted DNS request) + SharedKey [keySize]byte // Shared key that is to be used to encrypt/decrypt messages +} + +// Dial fetches and validates DNSCrypt certificate from the given server +// Data received during this call is then used for DNS requests encryption/decryption +// stampStr is an sdns:// address which is parsed using go-dnsstamps package +func (c *Client) Dial(stampStr string) (*ResolverInfo, error) { + stamp, err := dnsstamps.NewServerStampFromString(stampStr) + if err != nil { + // Invalid SDNS stamp + return nil, err + } + + if stamp.Proto != dnsstamps.StampProtoTypeDNSCrypt { + return nil, ErrInvalidDNSStamp + } + + return c.DialStamp(stamp) +} + +// DialStamp fetches and validates DNSCrypt certificate from the given server +// Data received during this call is then used for DNS requests encryption/decryption +func (c *Client) DialStamp(stamp dnsstamps.ServerStamp) (*ResolverInfo, error) { + resolverInfo := &ResolverInfo{} + + // Generate the secret/public pair + resolverInfo.SecretKey, resolverInfo.PublicKey = generateRandomKeyPair() + + // Set the provider properties + resolverInfo.ServerPublicKey = stamp.ServerPk + resolverInfo.ServerAddress = stamp.ServerAddrStr + resolverInfo.ProviderName = stamp.ProviderName + + cert, err := c.fetchCert(stamp) + if err != nil { + return nil, err + } + resolverInfo.ResolverCert = cert + + // Compute shared key that we'll use to encrypt/decrypt messages + sharedKey, err := computeSharedKey(cert.EsVersion, &resolverInfo.SecretKey, &cert.ResolverPk) + if err != nil { + return nil, err + } + resolverInfo.SharedKey = sharedKey + return resolverInfo, nil +} + +// Exchange performs a synchronous DNS query to the specified DNSCrypt server and returns a DNS response. +// This method creates a new network connection for every call so avoid using it for TCP. +// DNSCrypt cert needs to be fetched and validated prior to this call using the c.DialStamp method. +func (c *Client) Exchange(m *dns.Msg, resolverInfo *ResolverInfo) (*dns.Msg, error) { + network := "udp" + if c.Net == "tcp" { + network = "tcp" + } + + conn, err := net.Dial(network, resolverInfo.ServerAddress) + if err != nil { + return nil, err + } + defer conn.Close() + + r, err := c.ExchangeConn(conn, m, resolverInfo) + if err != nil { + return nil, err + } + return r, nil +} + +// ExchangeConn performs a synchronous DNS query to the specified DNSCrypt server and returns a DNS response. +// DNSCrypt server information needs to be fetched and validated prior to this call using the c.DialStamp method +func (c *Client) ExchangeConn(conn net.Conn, m *dns.Msg, resolverInfo *ResolverInfo) (*dns.Msg, error) { + query, err := c.encrypt(m, resolverInfo) + if err != nil { + return nil, err + } + + err = c.writeQuery(conn, query) + if err != nil { + return nil, err + } + + b, err := c.readResponse(conn) + if err != nil { + return nil, err + } + + res, err := c.decrypt(b, resolverInfo) + if err != nil { + return nil, err + } + + return res, nil +} + +// writeQuery - writes query to the network connection +// depending on the protocol we may write a 2-byte prefix or not +func (c *Client) writeQuery(conn net.Conn, query []byte) error { + var err error + + if c.Timeout > 0 { + _ = conn.SetWriteDeadline(time.Now().Add(c.Timeout)) + } + + // Write to the connection + if _, ok := conn.(*net.TCPConn); ok { + l := make([]byte, 2) + binary.BigEndian.PutUint16(l, uint16(len(query))) + _, err = (&net.Buffers{l, query}).WriteTo(conn) + } else { + _, err = conn.Write(query) + } + + return err +} + +// readResponse - reads response from the network connection +// depending on the protocol, we may read a 2-byte prefix or not +func (c *Client) readResponse(conn net.Conn) ([]byte, error) { + if c.Timeout > 0 { + _ = conn.SetReadDeadline(time.Now().Add(c.Timeout)) + } + + proto := "udp" + if _, ok := conn.(*net.TCPConn); ok { + proto = "tcp" + } + + if proto == "udp" { + response := make([]byte, maxQueryLen) + n, err := conn.Read(response) + if err != nil { + return nil, err + } + return response[:n], nil + } + + // If we got here, this is a TCP connection + // so we should read a 2-byte prefix first + return readPrefixed(conn) +} + +// encrypt - encrypts a DNS message using shared key from the resolver info +func (c *Client) encrypt(m *dns.Msg, resolverInfo *ResolverInfo) ([]byte, error) { + q := EncryptedQuery{ + EsVersion: resolverInfo.ResolverCert.EsVersion, + ClientMagic: resolverInfo.ResolverCert.ClientMagic, + ClientPk: resolverInfo.PublicKey, + } + query, err := m.Pack() + if err != nil { + return nil, err + } + return q.Encrypt(query, resolverInfo.SharedKey) +} + +// decrypts - decrypts a DNS message using shared key from the resolver info +func (c *Client) decrypt(b []byte, resolverInfo *ResolverInfo) (*dns.Msg, error) { + dr := EncryptedResponse{ + EsVersion: resolverInfo.ResolverCert.EsVersion, + } + msg, err := dr.Decrypt(b, resolverInfo.SharedKey) + if err != nil { + return nil, err + } + + res := new(dns.Msg) + err = res.Unpack(msg) + if err != nil { + return nil, err + } + return res, nil +} + +// fetchCert - loads DNSCrypt cert from the specified server +func (c *Client) fetchCert(stamp dnsstamps.ServerStamp) (*Cert, error) { + providerName := stamp.ProviderName + if !strings.HasSuffix(providerName, ".") { + providerName = providerName + "." + } + + query := new(dns.Msg) + query.SetQuestion(providerName, dns.TypeTXT) + client := dns.Client{Net: c.Net, UDPSize: uint16(maxQueryLen), Timeout: c.Timeout} + r, _, err := client.Exchange(query, stamp.ServerAddrStr) + if err != nil { + return nil, err + } + + if r.Rcode != dns.RcodeSuccess { + return nil, ErrFailedToFetchCert + } + + var certErr error + currentCert := &Cert{} + foundValid := false + + for _, rr := range r.Answer { + txt, ok := rr.(*dns.TXT) + if !ok { + continue + } + var b []byte + b, certErr = unpackTxtString(strings.Join(txt.Txt, "")) + if certErr != nil { + log.Debug("[%s] failed to pack TXT record: %v", providerName, certErr) + continue + } + + cert := &Cert{} + certErr = cert.Deserialize(b) + if certErr != nil { + log.Debug("[%s] failed to deserialize cert: %v", providerName, certErr) + continue + } + + log.Debug("[%s] fetched certificate %d", providerName, cert.Serial) + + if !cert.VerifyDate() { + certErr = ErrInvalidDate + log.Debug("[%s] cert %d date is not valid", providerName, cert.Serial) + continue + } + + if !cert.VerifySignature(stamp.ServerPk) { + certErr = ErrInvalidCertSignature + log.Debug("[%s] cert %d signature is not valid", providerName, cert.Serial) + continue + } + + if cert.Serial < currentCert.Serial { + log.Debug("[%v] cert %d superseded by a previous certificate", providerName, cert.Serial) + continue + } + + if cert.Serial == currentCert.Serial { + if cert.EsVersion > currentCert.EsVersion { + log.Debug("[%v] Upgrading the construction from %v to %v", providerName, currentCert.EsVersion, cert.EsVersion) + } else { + log.Debug("[%v] Keeping the previous, preferred crypto construction", providerName) + continue + } + } + + // Setting the cert + currentCert = cert + foundValid = true + } + + if foundValid { + return currentCert, nil + } + + return nil, certErr +} diff --git a/client_test.go b/client_test.go new file mode 100644 index 0000000..e5f294a --- /dev/null +++ b/client_test.go @@ -0,0 +1,186 @@ +package dnscrypt + +import ( + "net" + "os" + "testing" + "time" + + "github.com/ameshkov/dnsstamps" + "github.com/miekg/dns" + "github.com/stretchr/testify/assert" +) + +func TestParseStamp(t *testing.T) { + // Google DoH + stampStr := "sdns://AgUAAAAAAAAAAAAOZG5zLmdvb2dsZS5jb20NL2V4cGVyaW1lbnRhbA" + stamp, err := dnsstamps.NewServerStampFromString(stampStr) + + if err != nil || stamp.ProviderName == "" { + t.Fatalf("Could not parse stamp %s: %s", stampStr, err) + } + + assert.Equal(t, stampStr, stamp.String()) + assert.Equal(t, dnsstamps.StampProtoTypeDoH, stamp.Proto) + assert.Equal(t, "dns.google.com", stamp.ProviderName) + assert.Equal(t, "/experimental", stamp.Path) + + // AdGuard DNSCrypt + stampStr = "sdns://AQIAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20" + stamp, err = dnsstamps.NewServerStampFromString(stampStr) + + if err != nil || stamp.ProviderName == "" { + t.Fatalf("Could not parse stamp %s: %s", stampStr, err) + } + + assert.Equal(t, stampStr, stamp.String()) + assert.Equal(t, dnsstamps.StampProtoTypeDNSCrypt, stamp.Proto) + assert.Equal(t, "2.dnscrypt.default.ns1.adguard.com", stamp.ProviderName) + assert.Equal(t, "", stamp.Path) + assert.Equal(t, "176.103.130.130:5443", stamp.ServerAddrStr) + assert.Equal(t, keySize, len(stamp.ServerPk)) +} + +func TestInvalidStamp(t *testing.T) { + client := Client{} + _, err := client.Dial("sdns://AQIAAAAAAAAAFDE") + assert.NotNil(t, err) +} + +func TestTimeoutOnDialError(t *testing.T) { + // AdGuard DNS pointing to a wrong IP + stampStr := "sdns://AQIAAAAAAAAADDguOC44Ljg6NTQ0MyDRK0fyUtzywrv4mRCG6vec5EldixbIoMQyLlLKPzkIcyIyLmRuc2NyeXB0LmRlZmF1bHQubnMxLmFkZ3VhcmQuY29t" + client := Client{Timeout: 300 * time.Millisecond} + + _, err := client.Dial(stampStr) + assert.NotNil(t, err) + assert.True(t, os.IsTimeout(err)) +} + +func TestTimeoutOnDialExchange(t *testing.T) { + // AdGuard DNS + stampStr := "sdns://AQIAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20" + client := Client{Timeout: 300 * time.Millisecond} + + serverInfo, err := client.Dial(stampStr) + assert.Nil(t, err) + + // Point it to an IP where there's no DNSCrypt server + serverInfo.ServerAddress = "8.8.8.8:5443" + req := createTestMessage() + + // Do exchange + _, err = client.Exchange(req, serverInfo) + + // Check error + assert.NotNil(t, err) + assert.True(t, os.IsTimeout(err)) +} + +func TestFetchCertPublicResolvers(t *testing.T) { + stamps := []struct { + stampStr string + }{ + { + // AdGuard DNS + stampStr: "sdns://AQIAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20", + }, + { + // AdGuard DNS Family + stampStr: "sdns://AQIAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMjo1NDQzILgxXdexS27jIKRw3C7Wsao5jMnlhvhdRUXWuMm1AFq6ITIuZG5zY3J5cHQuZmFtaWx5Lm5zMS5hZGd1YXJkLmNvbQ", + }, + { + // AdGuard DNS Unfiltered + stampStr: "sdns://AQIAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzNjo1NDQzILXoRNa4Oj4-EmjraB--pw3jxfpo29aIFB2_LsBmstr6JTIuZG5zY3J5cHQudW5maWx0ZXJlZC5uczEuYWRndWFyZC5jb20", + }, + { + // Cisco OpenDNS + stampStr: "sdns://AQAAAAAAAAAADjIwOC42Ny4yMjAuMjIwILc1EUAgbyJdPivYItf9aR6hwzzI1maNDL4Ev6vKQ_t5GzIuZG5zY3J5cHQtY2VydC5vcGVuZG5zLmNvbQ", + }, + { + // Cisco OpenDNS Family Shield + stampStr: "sdns://AQAAAAAAAAAADjIwOC42Ny4yMjAuMTIzILc1EUAgbyJdPivYItf9aR6hwzzI1maNDL4Ev6vKQ_t5GzIuZG5zY3J5cHQtY2VydC5vcGVuZG5zLmNvbQ", + }, + } + + for _, test := range stamps { + stamp, err := dnsstamps.NewServerStampFromString(test.stampStr) + assert.Nil(t, err) + + t.Run(stamp.ProviderName, func(t *testing.T) { + c := &Client{Net: "udp"} + resolverInfo, err := c.DialStamp(stamp) + assert.Nil(t, err) + assert.NotNil(t, resolverInfo) + assert.True(t, resolverInfo.ResolverCert.VerifyDate()) + assert.True(t, resolverInfo.ResolverCert.VerifySignature(stamp.ServerPk)) + }) + } +} + +func TestExchangePublicResolvers(t *testing.T) { + stamps := []struct { + stampStr string + }{ + { + // AdGuard DNS + stampStr: "sdns://AQIAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20", + }, + { + // AdGuard DNS Family + stampStr: "sdns://AQIAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMjo1NDQzILgxXdexS27jIKRw3C7Wsao5jMnlhvhdRUXWuMm1AFq6ITIuZG5zY3J5cHQuZmFtaWx5Lm5zMS5hZGd1YXJkLmNvbQ", + }, + { + // AdGuard DNS Unfiltered + stampStr: "sdns://AQIAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzNjo1NDQzILXoRNa4Oj4-EmjraB--pw3jxfpo29aIFB2_LsBmstr6JTIuZG5zY3J5cHQudW5maWx0ZXJlZC5uczEuYWRndWFyZC5jb20", + }, + { + // Cisco OpenDNS + stampStr: "sdns://AQAAAAAAAAAADjIwOC42Ny4yMjAuMjIwILc1EUAgbyJdPivYItf9aR6hwzzI1maNDL4Ev6vKQ_t5GzIuZG5zY3J5cHQtY2VydC5vcGVuZG5zLmNvbQ", + }, + { + // Cisco OpenDNS Family Shield + stampStr: "sdns://AQAAAAAAAAAADjIwOC42Ny4yMjAuMTIzILc1EUAgbyJdPivYItf9aR6hwzzI1maNDL4Ev6vKQ_t5GzIuZG5zY3J5cHQtY2VydC5vcGVuZG5zLmNvbQ", + }, + } + + for _, test := range stamps { + stamp, err := dnsstamps.NewServerStampFromString(test.stampStr) + assert.Nil(t, err) + + t.Run(stamp.ProviderName, func(t *testing.T) { + checkDNSCryptServer(t, test.stampStr, "udp") + checkDNSCryptServer(t, test.stampStr, "tcp") + }) + } +} + +func checkDNSCryptServer(t *testing.T, stampStr string, network string) { + client := Client{Net: network, Timeout: 10 * time.Second} + resolverInfo, err := client.Dial(stampStr) + assert.Nil(t, err) + + req := createTestMessage() + + reply, err := client.Exchange(req, resolverInfo) + assert.Nil(t, err) + assertTestMessageResponse(t, reply) +} + +func createTestMessage() *dns.Msg { + req := dns.Msg{} + req.Id = dns.Id() + req.RecursionDesired = true + req.Question = []dns.Question{ + {Name: "google-public-dns-a.google.com.", Qtype: dns.TypeA, Qclass: dns.ClassINET}, + } + return &req +} + +func assertTestMessageResponse(t *testing.T, reply *dns.Msg) { + assert.NotNil(t, reply) + assert.Equal(t, 1, len(reply.Answer)) + a, ok := reply.Answer[0].(*dns.A) + assert.True(t, ok) + assert.Equal(t, net.IPv4(8, 8, 8, 8).To4(), a.A.To4()) +} diff --git a/cmd/generate.go b/cmd/generate.go new file mode 100644 index 0000000..2ad29dc --- /dev/null +++ b/cmd/generate.go @@ -0,0 +1,50 @@ +package main + +import ( + "io/ioutil" + + "github.com/AdguardTeam/golibs/log" + "github.com/ameshkov/dnscrypt" + "gopkg.in/yaml.v3" +) + +// GenerateArgs - "generate" command arguments +type GenerateArgs struct { + ProviderName string `short:"p" long:"provider-name" description:"DNSCrypt provider name" required:"true"` + PrivateKey string `short:"k" long:"private-key" description:"Private key (hex-encoded)"` + CertificateTTL int `short:"t" long:"ttl" description:"Certificate time-to-live (seconds)"` + Out string `short:"o" long:"out" description:"Path to the resulting config file" required:"true"` +} + +// generate - generates DNSCrypt server configuration +func generate(args GenerateArgs) { + log.Info("Generating configuration for %s", args.ProviderName) + + var privateKey []byte + var err error + if args.PrivateKey != "" { + privateKey, err = dnscrypt.HexDecodeKey(args.PrivateKey) + if err != nil { + log.Fatalf("failed to generate private key: %v", err) + } + } + + rc, err := dnscrypt.GenerateResolverConfig(args.ProviderName, privateKey) + if err != nil { + log.Fatalf("failed to generate resolver config: %v", err) + } + + b, err := yaml.Marshal(rc) + if err != nil { + log.Fatalf("failed to serialize to yaml: %v", err) + } + + // nolint + err = ioutil.WriteFile(args.Out, b, 0644) + if err != nil { + log.Fatalf("failed to save %s: %v", args.Out, err) + } + + log.Info("Configuration has been written to %s", args.Out) + log.Info("Go to https://dnscrypt.info/stamps to generate an SDNS stamp") +} diff --git a/cmd/lookup.go b/cmd/lookup.go new file mode 100644 index 0000000..eeaea70 --- /dev/null +++ b/cmd/lookup.go @@ -0,0 +1,110 @@ +package main + +import ( + "encoding/json" + "os" + "strings" + "time" + + "github.com/AdguardTeam/golibs/log" + "github.com/ameshkov/dnscrypt" + "github.com/ameshkov/dnsstamps" + "github.com/miekg/dns" +) + +// LookupStampArgs - "lookup-stamp" command arguments +type LookupStampArgs struct { + Stamp string `short:"s" long:"stamp" description:"DNSCrypt resolver stamp" required:"true"` + Domain string `short:"d" long:"domain" description:"Domain to resolve" required:"true"` + Type string `short:"t" long:"type" description:"DNS query type" default:"A"` +} + +// LookupArgs - "lookup" command arguments +type LookupArgs struct { + ProviderName string `short:"p" long:"provider-name" description:"DNSCrypt resolver provider name" required:"true"` + PublicKey string `short:"k" long:"public-key" description:"DNSCrypt resolver public key" required:"true"` + ServerAddr string `short:"a" long:"addr" description:"Resolver address (IP[:port]). By default, the port is 443" required:"true"` + Domain string `short:"d" long:"domain" description:"Domain to resolve" required:"true"` + Type string `short:"t" long:"type" description:"DNS query type" default:"A"` +} + +// LookupResult - lookup result that contains the cert info and the query response +type LookupResult struct { + Certificate struct { + Serial uint32 `json:"serial"` + EsVersion string `json:"encryption"` + NotAfter time.Time `json:"not_after"` + NotBefore time.Time `json:"not_before"` + } `json:"certificate"` + + Reply *dns.Msg `json:"reply"` +} + +// lookup - performs a DNS lookup, prints DNSCrypt info and lookup results +func lookup(args LookupArgs) { + serverPk, err := dnscrypt.HexDecodeKey(args.PublicKey) + if err != nil { + log.Fatalf("invalid resolver public key: %v", err) + } + + stamp := dnsstamps.ServerStamp{ + ProviderName: args.ProviderName, + ServerPk: serverPk, + ServerAddrStr: args.ServerAddr, + Proto: dnsstamps.StampProtoTypeDNSCrypt, + } + + lookupStamp(LookupStampArgs{ + Stamp: stamp.String(), + Domain: args.Domain, + Type: args.Type, + }) +} + +// lookupStamp - performs a DNS lookup, prints DNSCrypt cert info and lookup results +func lookupStamp(args LookupStampArgs) { + c := &dnscrypt.Client{ + Net: "udp", + Timeout: 10 * time.Second, + } + ri, err := c.Dial(args.Stamp) + + if err != nil { + log.Fatalf("failed to establish connection with the server: %v", err) + } + + res := LookupResult{} + res.Certificate.Serial = ri.ResolverCert.Serial + res.Certificate.NotAfter = time.Unix(int64(ri.ResolverCert.NotAfter), 0) + res.Certificate.NotBefore = time.Unix(int64(ri.ResolverCert.NotBefore), 0) + res.Certificate.EsVersion = ri.ResolverCert.EsVersion.String() + + dnsType, ok := dns.StringToType[strings.ToUpper(args.Type)] + if !ok { + log.Fatalf("invalid type %s", args.Type) + } + + req := &dns.Msg{} + req.Id = dns.Id() + req.RecursionDesired = true + req.Question = []dns.Question{ + { + Name: dns.Fqdn(args.Domain), + Qtype: dnsType, + Qclass: dns.ClassINET, + }, + } + + reply, err := c.Exchange(req, ri) + if err != nil { + log.Fatalf("failed to resolve %s %s", args.Type, args.Domain) + } + + res.Reply = reply + b, err := json.MarshalIndent(res, "", " ") + if err != nil { + log.Fatalf("failed to marshal result to json: %v", err) + } + + _, _ = os.Stdout.WriteString(string(b) + "\n") +} diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..ce818b5 --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,51 @@ +package main + +import ( + "os" + + "github.com/AdguardTeam/golibs/log" + + goFlags "github.com/jessevdk/go-flags" +) + +// Options - command-line options +type Options struct { + Generate GenerateArgs `command:"generate" description:"Generates DNSCrypt server configuration"` + LookupStamp LookupStampArgs `command:"lookup-stamp" description:"Performs a DNSCrypt lookup for the specified domain using an sdns:// stamp"` + Lookup LookupArgs `command:"lookup" description:"Performs a DNSCrypt lookup for the specified domain"` + Server ServerArgs `command:"server" description:"Runs a DNSCrypt resolver"` + Version struct { + } `command:"version" description:"Prints version"` +} + +// VersionString will be set through ldflags, contains current version +var VersionString = "1.0" + +func main() { + var opts Options + + var parser = goFlags.NewParser(&opts, goFlags.Default) + _, err := parser.Parse() + if err != nil { + if flagsErr, ok := err.(*goFlags.Error); ok && flagsErr.Type == goFlags.ErrHelp { + os.Exit(0) + } else { + os.Exit(1) + } + } + + switch parser.Active.Name { + case "version": + log.Printf("dnscrypt version %s\n", VersionString) + case "generate": + generate(opts.Generate) + case "lookup-stamp": + lookupStamp(opts.LookupStamp) + case "lookup": + lookup(opts.Lookup) + case "server": + server(opts.Server) + default: + log.Fatalf("unknown command %s", parser.Active.Name) + } +} diff --git a/cmd/server.go b/cmd/server.go new file mode 100644 index 0000000..67c5e5f --- /dev/null +++ b/cmd/server.go @@ -0,0 +1,111 @@ +package main + +import ( + "io/ioutil" + "net" + "os" + "os/signal" + "syscall" + + "github.com/AdguardTeam/golibs/log" + "github.com/ameshkov/dnscrypt" + "github.com/miekg/dns" + "gopkg.in/yaml.v3" +) + +// ServerArgs - "server" command arguments +type ServerArgs struct { + Config string `short:"c" long:"config" description:"Path to the DNSCrypt configuration file" required:"true"` + Forward string `short:"f" long:"forward" description:"Forwards DNS queries to the specified address" default:"94.140.14.140:53"` + ListenAddrs []string `short:"l" long:"listen" description:"Listening addresses" default:"0.0.0.0"` + ListenPorts []int `short:"p" long:"port" description:"Listening ports" default:"443"` +} + +// server - runs a DNSCrypt server +func server(args ServerArgs) { + log.Info("Starting DNSCrypt server") + + b, err := ioutil.ReadFile(args.Config) + if err != nil { + log.Fatalf("failed to read the configuration: %v", err) + } + + rc := dnscrypt.ResolverConfig{} + err = yaml.Unmarshal(b, &rc) + if err != nil { + log.Fatalf("failed to deserialize configuration: %v", err) + } + + cert, err := rc.CreateCert() + if err != nil { + log.Fatalf("failed to generate certificate: %v", err) + } + + s := &dnscrypt.Server{ + ProviderName: rc.ProviderName, + ResolverCert: cert, + Handler: &forwardHandler{addr: args.Forward}, + } + + tcp, udp := createListeners(args) + for _, t := range tcp { + log.Info("Listening to tcp://%s", t.Addr().String()) + listen := t + go func() { _ = s.ServeTCP(listen) }() + } + for _, u := range udp { + log.Info("Listening to udp://%s", u.LocalAddr().String()) + listen := u + go func() { _ = s.ServeUDP(listen) }() + } + + signalChannel := make(chan os.Signal, 1) + signal.Notify(signalChannel, syscall.SIGINT, syscall.SIGTERM) + <-signalChannel + + log.Info("Closing all listeners") + for _, t := range tcp { + _ = t.Close() + } + for _, u := range udp { + _ = u.Close() + } +} + +// createListeners - creates listeners for our server +func createListeners(args ServerArgs) (tcp []net.Listener, udp []*net.UDPConn) { + for _, addr := range args.ListenAddrs { + ip := net.ParseIP(addr) + if ip == nil { + log.Fatalf("invalid listen address: %s", addr) + } + + for _, port := range args.ListenPorts { + tcpListen, err := net.ListenTCP("tcp", &net.TCPAddr{IP: ip, Port: port}) + if err != nil { + log.Fatalf("failed to start TCP listener: %v", err) + } + udpListen, err := net.ListenUDP("udp", &net.UDPAddr{IP: ip, Port: port}) + if err != nil { + log.Fatalf("failed to start UDP listener: %v", err) + } + tcp = append(tcp, tcpListen) + udp = append(udp, udpListen) + } + } + + return +} + +type forwardHandler struct { + addr string +} + +// ServeDNS - implements Handler interface +func (f *forwardHandler) ServeDNS(rw dnscrypt.ResponseWriter, r *dns.Msg) error { + res, err := dns.Exchange(r, f.addr) + if err != nil { + return err + } + return rw.WriteMsg(res) +} diff --git a/constants.go b/constants.go new file mode 100644 index 0000000..81a2cf2 --- /dev/null +++ b/constants.go @@ -0,0 +1,113 @@ +package dnscrypt + +import "errors" + +var ( + // ErrTooShort - DNS query is shorter than possible + ErrTooShort = errors.New("DNSCrypt message is too short") + + // ErrQueryTooLarge - DNS query is larger than max allowed size + ErrQueryTooLarge = errors.New("DNSCrypt query is too large") + + // ErrEsVersion - cert contains unsupported es-version + ErrEsVersion = errors.New("unsupported es-version") + + // ErrInvalidDate - cert is not valid for the current time + ErrInvalidDate = errors.New("cert has invalid ts-start or ts-end") + + // ErrInvalidCertSignature - cert has invalid signature + ErrInvalidCertSignature = errors.New("cert has invalid signature") + + // ErrInvalidQuery - failed to decrypt a DNSCrypt query + ErrInvalidQuery = errors.New("DNSCrypt query is invalid and cannot be decrypted") + + // ErrInvalidClientMagic - client-magic does not match + ErrInvalidClientMagic = errors.New("DNSCrypt query contains invalid client magic") + + // ErrInvalidResolverMagic - server-magic does not match + ErrInvalidResolverMagic = errors.New("DNSCrypt response contains invalid resolver magic") + + // ErrInvalidResponse - failed to decrypt a DNSCrypt response + ErrInvalidResponse = errors.New("DNSCrypt response is invalid and cannot be decrypted") + + // ErrInvalidPadding - failed to unpad a query + ErrInvalidPadding = errors.New("invalid padding") + + // ErrInvalidDNSStamp - invalid DNS stamp + ErrInvalidDNSStamp = errors.New("invalid DNS stamp") + + // ErrFailedToFetchCert - failed to fetch DNSCrypt certificate + ErrFailedToFetchCert = errors.New("failed to fetch DNSCrypt certificate") + + // ErrCertTooShort - failed to deserialize cert, too short + ErrCertTooShort = errors.New("cert is too short") + + // ErrCertMagic - invalid cert magic + ErrCertMagic = errors.New("invalid cert magic") + + // ErrServerConfig - failed to start the DNSCrypt server - invalid configuration + ErrServerConfig = errors.New("invalid server configuration") +) + +const ( + // is a variable length, initially set to 256 bytes, and + // must be a multiple of 64 bytes. (see https://dnscrypt.info/protocol) + // Some servers do not work if padded length is less than 256. Example: Quad9 + minUDPQuestionSize = 256 + + // - maximum allowed query length + maxQueryLen = 1252 + + // Minimum possible DNS packet size + minDNSPacketSize = 12 + 5 + + // See 11. Authenticated encryption and key exchange algorithm + // The public and secret keys are 32 bytes long in storage + keySize = 32 + + // size of the shared key used to encrypt/decrypt messages + sharedKeySize = 32 + + // ClientMagic - the first 8 bytes of a client query that is to be built + // using the information from this certificate. It may be a truncated + // public key. Two valid certificates cannot share the same . + clientMagicSize = 8 + + // When using X25519-XSalsa20Poly1305, this construction requires a 24 bytes + // nonce, that must not be reused for a given shared secret. + nonceSize = 24 + + // the first 8 bytes of every dnscrypt response. must match resolverMagic. + resolverMagicSize = 8 +) + +var ( + // certMagic - bytes sequence that must be in the beginning of the serialized cert + certMagic = [4]byte{0x44, 0x4e, 0x53, 0x43} + + // resolverMagic - byte sequence that must be in the beginning of every response + resolverMagic = []byte{0x72, 0x36, 0x66, 0x6e, 0x76, 0x57, 0x6a, 0x38} +) + +// CryptoConstruction represents the encryption algorithm (either XSalsa20Poly1305 or XChacha20Poly1305) +type CryptoConstruction uint16 + +const ( + // UndefinedConstruction is the default value for empty CertInfo only + UndefinedConstruction CryptoConstruction = iota + // XSalsa20Poly1305 encryption + XSalsa20Poly1305 CryptoConstruction = 0x0001 + // XChacha20Poly1305 encryption + XChacha20Poly1305 CryptoConstruction = 0x0002 +) + +func (c CryptoConstruction) String() string { + switch c { + case XChacha20Poly1305: + return "XChacha20Poly1305" + case XSalsa20Poly1305: + return "XSalsa20Poly1305" + default: + return "Unknown" + } +} diff --git a/dnscrypt.go b/dnscrypt.go deleted file mode 100644 index ff0dd60..0000000 --- a/dnscrypt.go +++ /dev/null @@ -1,551 +0,0 @@ -package dnscrypt - -import ( - "bytes" - "encoding/binary" - "errors" - "fmt" - "log" - "math/rand" - "net" - "strings" - "time" - - "github.com/ameshkov/dnscrypt/xsecretbox" - "github.com/ameshkov/dnsstamps" - "github.com/miekg/dns" - "golang.org/x/crypto/curve25519" - "golang.org/x/crypto/ed25519" - "golang.org/x/crypto/nacl/box" - "golang.org/x/crypto/nacl/secretbox" -) - -// CryptoConstruction represents the encryption algorithm (either XSalsa20Poly1305 or XChacha20Poly1305) -type CryptoConstruction uint16 - -const ( - // UndefinedConstruction is the default value for empty CertInfo only - UndefinedConstruction CryptoConstruction = iota - // XSalsa20Poly1305 encryption - XSalsa20Poly1305 - // XChacha20Poly1305 encryption - XChacha20Poly1305 -) - -var ( - certMagic = [4]byte{0x44, 0x4e, 0x53, 0x43} - serverMagic = [8]byte{0x72, 0x36, 0x66, 0x6e, 0x76, 0x57, 0x6a, 0x38} - minDNSPacketSize = 12 + 5 - maxDNSPacketSize = 4096 - maxDNSUDPPacketSize = 1252 -) - -const ( - clientMagicLen = 8 - nonceSize = xsecretbox.NonceSize - halfNonceSize = xsecretbox.NonceSize / 2 - tagSize = xsecretbox.TagSize - publicKeySize = 32 - queryOverhead = clientMagicLen + publicKeySize + halfNonceSize + tagSize - - // is a variable length, initially set to 256 bytes, and - // must be a multiple of 64 bytes. (see https://dnscrypt.info/protocol) - // Some servers do not work if padded length is less than 256. Example: Quad9 - minUDPQuestionSize = 256 -) - -// Client contains parameters for a DNSCrypt client -type Client struct { - Proto string // Protocol ("udp" or "tcp"). Empty means "udp". - Timeout time.Duration // Timeout for read/write operations (0 means infinite timeout) - AdjustPayloadSize bool // If true, the client will automatically add a EDNS0 RR that will advertise a larger buffer -} - -// CertInfo contains DnsCrypt server certificate data retrieved from the server -type CertInfo struct { - Serial uint32 // Cert serial number (the cert can be superseded by another one with a higher serial number) - ServerPk [32]byte // Server public key - SharedKey [32]byte // Shared key - MagicQuery [clientMagicLen]byte - CryptoConstruction CryptoConstruction // Encryption algorithm - NotBefore uint32 // Cert is valid starting from this date (epoch time) - NotAfter uint32 // Cert is valid until this date (epoch time) -} - -// ServerInfo contains DNSCrypt server information necessary for decryption/encryption -type ServerInfo struct { - SecretKey [32]byte // Client secret key - PublicKey [32]byte // Client public key - ServerPublicKey ed25519.PublicKey // Server public key - ServerAddress string // Server IP address - ProviderName string // Provider name - - ServerCert *CertInfo // Certificate info (obtained with the first unencrypted DNS request) -} - -// Dial fetches and validates DNSCrypt certificate from the given server -// Data received during this call is then used for DNS requests encryption/decryption -// stampStr is an sdns:// address which is parsed using go-dnsstamps package -func (c *Client) Dial(stampStr string) (*ServerInfo, time.Duration, error) { - - stamp, err := dnsstamps.NewServerStampFromString(stampStr) - if err != nil { - // Invalid SDNS stamp - return nil, 0, err - } - - if stamp.Proto != dnsstamps.StampProtoTypeDNSCrypt { - return nil, 0, errors.New("stamp is not for a DNSCrypt server") - } - - return c.DialStamp(stamp) -} - -// DialStamp fetches and validates DNSCrypt certificate from the given server -// Data received during this call is then used for DNS requests encryption/decryption -func (c *Client) DialStamp(stamp dnsstamps.ServerStamp) (*ServerInfo, time.Duration, error) { - - serverInfo := ServerInfo{} - - // Generate the secret/public pair - if _, err := rand.Read(serverInfo.SecretKey[:]); err != nil { - return nil, 0, err - } - curve25519.ScalarBaseMult(&serverInfo.PublicKey, &serverInfo.SecretKey) - - // Set the provider properties - serverInfo.ServerPublicKey = stamp.ServerPk - serverInfo.ServerAddress = stamp.ServerAddrStr - serverInfo.ProviderName = stamp.ProviderName - if !strings.HasSuffix(serverInfo.ProviderName, ".") { - serverInfo.ProviderName = serverInfo.ProviderName + "." - } - - // Fetch the certificate and validate it - certInfo, rtt, err := serverInfo.fetchCurrentDNSCryptCert(c.Proto, c.Timeout) - - if err != nil { - return nil, rtt, err - } - - serverInfo.ServerCert = &certInfo - return &serverInfo, rtt, nil -} - -// Exchange performs a synchronous DNS query to the specified DNSCrypt server and returns a DNS response. -// This method creates a new network connection for every call so avoid using it for TCP. -// DNSCrypt server information needs to be fetched and validated prior to this call using the c.DialStamp method. -func (c *Client) Exchange(m *dns.Msg, s *ServerInfo) (*dns.Msg, time.Duration, error) { - - now := time.Now() - network := c.Proto - if network == "" { - network = "udp" - } - conn, err := net.Dial(network, s.ServerAddress) - if err != nil { - return nil, 0, err - } - defer conn.Close() - - r, _, err := c.ExchangeConn(m, s, conn) - if err != nil { - return nil, 0, err - } - - rtt := time.Since(now) - return r, rtt, nil -} - -// ExchangeConn performs a synchronous DNS query to the specified DNSCrypt server and returns a DNS response. -// DNSCrypt server information needs to be fetched and validated prior to this call using the c.DialStamp method -func (c *Client) ExchangeConn(m *dns.Msg, s *ServerInfo, conn net.Conn) (*dns.Msg, time.Duration, error) { - now := time.Now() - - if c.AdjustPayloadSize { - c.adjustPayloadSize(m) - } - query, err := m.Pack() - if err != nil { - return nil, 0, err - } - - encryptedQuery, clientNonce, err := s.encrypt(c.Proto, query) - if err != nil { - return nil, 0, err - } - - if c.Proto == "tcp" { - encryptedQuery, err = prefixWithSize(encryptedQuery) - if err != nil { - return nil, 0, err - } - } - - if c.Timeout > 0 { - _ = conn.SetDeadline(time.Now().Add(c.Timeout)) - } - _, _ = conn.Write(encryptedQuery) - encryptedResponse := make([]byte, maxDNSPacketSize) - - // Reading the response - // In case if the server ServerInfo is not valid anymore (for instance, certificate was rotated) the read operation will most likely time out. - // This might be a signal to re-dial for the server certificate. - if c.Proto == "tcp" { - encryptedResponse, err = readPrefixed(conn) - if err != nil { - return nil, 0, err - } - } else { - length, readErr := conn.Read(encryptedResponse) - if readErr != nil { - return nil, 0, readErr - } - encryptedResponse = encryptedResponse[:length] - } - - decrypted, err := s.decrypt(encryptedResponse, clientNonce) - if err != nil { - return nil, 0, err - } - - r := dns.Msg{} - err = r.Unpack(decrypted) - if err != nil { - return nil, 0, err - } - - rtt := time.Since(now) - return &r, rtt, nil -} - -// Adjusts the maximum payload size advertised in queries sent to upstream servers -// See https://github.com/jedisct1/dnscrypt-proxy/blob/master/dnscrypt-proxy/plugin_get_set_payload_size.go -// See here also: https://github.com/jedisct1/dnscrypt-proxy/issues/667 -func (c *Client) adjustPayloadSize(msg *dns.Msg) { - originalMaxPayloadSize := dns.MinMsgSize - edns0 := msg.IsEdns0() - dnssec := false - if edns0 != nil { - originalMaxPayloadSize = int(edns0.UDPSize()) - dnssec = edns0.Do() - } - var options *[]dns.EDNS0 - - maxPayloadSize := min(maxDNSUDPPacketSize, max(originalMaxPayloadSize, maxDNSUDPPacketSize)) - - if maxPayloadSize > dns.MinMsgSize { - var extra2 []dns.RR - for _, extra := range msg.Extra { - if extra.Header().Rrtype != dns.TypeOPT { - extra2 = append(extra2, extra) - } else if xoptions := &extra.(*dns.OPT).Option; len(*xoptions) > 0 && options == nil { - options = xoptions - } - } - msg.Extra = extra2 - msg.SetEdns0(uint16(maxPayloadSize), dnssec) - if options != nil { - for _, extra := range msg.Extra { - if extra.Header().Rrtype == dns.TypeOPT { - extra.(*dns.OPT).Option = *options - break - } - } - } - } -} - -func (s *ServerInfo) fetchCurrentDNSCryptCert(proto string, timeout time.Duration) (CertInfo, time.Duration, error) { - if len(s.ServerPublicKey) != ed25519.PublicKeySize { - return CertInfo{}, 0, errors.New("invalid public key length") - } - - query := new(dns.Msg) - query.SetQuestion(s.ProviderName, dns.TypeTXT) - client := dns.Client{Net: proto, UDPSize: uint16(maxDNSUDPPacketSize), Timeout: timeout} - in, rtt, err := client.Exchange(query, s.ServerAddress) - if err != nil { - return CertInfo{}, 0, err - } - - certInfo := CertInfo{CryptoConstruction: UndefinedConstruction} - for _, answerRr := range in.Answer { - recCertInfo, err := txtToCertInfo(answerRr, s) - - if err != nil { - log.Printf("[%v] %s", s.ProviderName, err) - continue - } - - if recCertInfo.Serial < certInfo.Serial { - log.Printf("[%v] Superseded by a previous certificate", s.ProviderName) - continue - } - - if recCertInfo.Serial == certInfo.Serial { - if recCertInfo.CryptoConstruction > certInfo.CryptoConstruction { - log.Printf("[%v] Upgrading the construction from %v to %v", s.ProviderName, certInfo.CryptoConstruction, recCertInfo.CryptoConstruction) - } else { - log.Printf("[%v] Keeping the previous, preferred crypto construction", s.ProviderName) - continue - } - } - - // Set the cert info - certInfo = recCertInfo - } - - if certInfo.CryptoConstruction == UndefinedConstruction { - return certInfo, 0, errors.New("no useable certificate found") - } - - return certInfo, rtt, nil -} - -func (s *ServerInfo) encrypt(proto string, packet []byte) (encrypted []byte, clientNonce []byte, err error) { - nonce, clientNonce := make([]byte, nonceSize), make([]byte, halfNonceSize) - rand.Read(clientNonce) - copy(nonce, clientNonce) - var publicKey *[publicKeySize]byte - - sharedKey := &s.ServerCert.SharedKey - publicKey = &s.PublicKey - - minQuestionSize := queryOverhead + len(packet) - if proto == "tcp" { - var xpad [1]byte - rand.Read(xpad[:]) - minQuestionSize += int(xpad[0]) - } else { - minQuestionSize = max(minUDPQuestionSize, minQuestionSize) - } - paddedLength := min(maxDNSUDPPacketSize, (max(minQuestionSize, queryOverhead)+63) & ^63) - - if queryOverhead+len(packet)+1 > paddedLength { - err = errors.New("question too large; cannot be padded") - return - } - encrypted = append(s.ServerCert.MagicQuery[:], publicKey[:]...) - encrypted = append(encrypted, nonce[:halfNonceSize]...) - padded := pad(packet, paddedLength-queryOverhead) - if s.ServerCert.CryptoConstruction == XChacha20Poly1305 { - encrypted = xsecretbox.Seal(encrypted, nonce, padded, sharedKey[:]) - } else { - var xsalsaNonce [24]byte - copy(xsalsaNonce[:], nonce) - encrypted = secretbox.Seal(encrypted, padded, &xsalsaNonce, sharedKey) - } - return -} - -func (s *ServerInfo) decrypt(encrypted []byte, nonce []byte) ([]byte, error) { - - sharedKey := &s.ServerCert.SharedKey - serverMagicLen := len(serverMagic) - responseHeaderLen := serverMagicLen + nonceSize - if len(encrypted) < responseHeaderLen+tagSize+minDNSPacketSize || - len(encrypted) > responseHeaderLen+tagSize+maxDNSPacketSize || - !bytes.Equal(encrypted[:serverMagicLen], serverMagic[:]) { - return encrypted, errors.New("invalid message size or prefix") - } - serverNonce := encrypted[serverMagicLen:responseHeaderLen] - if !bytes.Equal(nonce[:halfNonceSize], serverNonce[:halfNonceSize]) { - return encrypted, errors.New("unexpected nonce") - } - var packet []byte - var err error - if s.ServerCert.CryptoConstruction == XChacha20Poly1305 { - packet, err = xsecretbox.Open(nil, serverNonce, encrypted[responseHeaderLen:], sharedKey[:]) - } else { - var xsalsaServerNonce [24]byte - copy(xsalsaServerNonce[:], serverNonce) - var ok bool - packet, ok = secretbox.Open(nil, encrypted[responseHeaderLen:], &xsalsaServerNonce, sharedKey) - if !ok { - err = errors.New("incorrect tag") - } - } - if err != nil { - return encrypted, err - } - packet, err = unpad(packet) - if err != nil || len(packet) < minDNSPacketSize { - return encrypted, errors.New("incorrect padding") - } - return packet, nil -} - -func txtToCertInfo(answerRr dns.RR, serverInfo *ServerInfo) (CertInfo, error) { - now := uint32(time.Now().Unix()) - certInfo := CertInfo{CryptoConstruction: UndefinedConstruction} - - binCert, err := packTxtString(strings.Join(answerRr.(*dns.TXT).Txt, "")) - - // Validate the cert basic params - if err != nil { - return certInfo, errors.New("unable to unpack the certificate") - } - if len(binCert) < 124 { - return certInfo, errors.New("certificate is too short") - } - if !bytes.Equal(binCert[:4], certMagic[:4]) { - return certInfo, errors.New("invalid cert magic") - } - - switch esVersion := binary.BigEndian.Uint16(binCert[4:6]); esVersion { - case 0x0001: - certInfo.CryptoConstruction = XSalsa20Poly1305 - case 0x0002: - certInfo.CryptoConstruction = XChacha20Poly1305 - default: - return certInfo, fmt.Errorf("unsupported crypto construction: %v", esVersion) - } - - // Verify the server public key - signature := binCert[8:72] - signed := binCert[72:] - if !ed25519.Verify(serverInfo.ServerPublicKey, signed, signature) { - return certInfo, errors.New("incorrect signature") - } - - certInfo.Serial = binary.BigEndian.Uint32(binCert[112:116]) - - // Validate the certificate date - certInfo.NotBefore = binary.BigEndian.Uint32(binCert[116:120]) - certInfo.NotAfter = binary.BigEndian.Uint32(binCert[120:124]) - if certInfo.NotBefore >= certInfo.NotAfter { - return certInfo, fmt.Errorf("certificate ends before it starts (%v >= %v)", certInfo.NotBefore, certInfo.NotAfter) - } - if now > certInfo.NotAfter || now < certInfo.NotBefore { - return certInfo, errors.New("certificate not valid at the current date") - } - - var serverPk [32]byte - copy(serverPk[:], binCert[72:104]) - certInfo.SharedKey = computeSharedKey(certInfo.CryptoConstruction, &serverInfo.SecretKey, &serverPk, &serverInfo.ProviderName) - - copy(certInfo.ServerPk[:], serverPk[:]) - copy(certInfo.MagicQuery[:], binCert[104:112]) - - return certInfo, nil -} - -func computeSharedKey(cryptoConstruction CryptoConstruction, secretKey *[32]byte, serverPk *[32]byte, providerName *string) (sharedKey [32]byte) { - if cryptoConstruction == XChacha20Poly1305 { - var err error - sharedKey, err = xsecretbox.SharedKey(*secretKey, *serverPk) - if err != nil { - log.Printf("[%v] Weak public key", providerName) - } - } else { - box.Precompute(&sharedKey, serverPk, secretKey) - } - return -} - -func isDigit(b byte) bool { return b >= '0' && b <= '9' } - -func dddToByte(s []byte) byte { - return (s[0]-'0')*100 + (s[1]-'0')*10 + (s[2] - '0') -} - -func packTxtString(s string) ([]byte, error) { - bs := make([]byte, len(s)) - msg := make([]byte, 0) - copy(bs, s) - for i := 0; i < len(bs); i++ { - if bs[i] == '\\' { - i++ - if i == len(bs) { - break - } - if i+2 < len(bs) && isDigit(bs[i]) && isDigit(bs[i+1]) && isDigit(bs[i+2]) { - msg = append(msg, dddToByte(bs[i:])) - i += 2 - } else if bs[i] == 't' { - msg = append(msg, '\t') - } else if bs[i] == 'r' { - msg = append(msg, '\r') - } else if bs[i] == 'n' { - msg = append(msg, '\n') - } else { - msg = append(msg, bs[i]) - } - } else { - msg = append(msg, bs[i]) - } - } - return msg, nil -} - -func max(a, b int) int { - if a > b { - return a - } - return b -} - -func min(a, b int) int { - if a < b { - return a - } - return b -} - -func pad(packet []byte, minSize int) []byte { - packet = append(packet, 0x80) - for len(packet) < minSize { - packet = append(packet, 0) - } - return packet -} - -func unpad(packet []byte) ([]byte, error) { - for i := len(packet); ; { - if i == 0 { - return nil, errors.New("invalid padding (short packet)") - } - i-- - if packet[i] == 0x80 { - return packet[:i], nil - } else if packet[i] != 0x00 { - return nil, errors.New("invalid padding (delimiter not found)") - } - } -} - -func prefixWithSize(packet []byte) ([]byte, error) { - packetLen := len(packet) - if packetLen > 0xffff { - return packet, errors.New("packet too large") - } - packet = append(append(packet, 0), 0) - copy(packet[2:], packet[:len(packet)-2]) - binary.BigEndian.PutUint16(packet[0:2], uint16(len(packet)-2)) - return packet, nil -} - -func readPrefixed(conn net.Conn) ([]byte, error) { - buf := make([]byte, 2+maxDNSPacketSize) - packetLength, pos := -1, 0 - for { - readnb, err := conn.Read(buf[pos:]) - if err != nil { - return buf, err - } - pos += readnb - if pos >= 2 && packetLength < 0 { - packetLength = int(binary.BigEndian.Uint16(buf[0:2])) - if packetLength > maxDNSPacketSize-1 { - return buf, errors.New("packet too large") - } - if packetLength < minDNSPacketSize { - return buf, errors.New("packet too short") - } - } - if packetLength >= 0 && pos >= 2+packetLength { - return buf[2 : 2+packetLength], nil - } - } -} diff --git a/dnscrypt_test.go b/dnscrypt_test.go deleted file mode 100644 index 690bd7f..0000000 --- a/dnscrypt_test.go +++ /dev/null @@ -1,164 +0,0 @@ -package dnscrypt - -import ( - "log" - "net" - "os" - "testing" - "time" - - "github.com/ameshkov/dnsstamps" - "github.com/miekg/dns" -) - -func TestParseStamp(t *testing.T) { - // Google DoH - stampStr := "sdns://AgUAAAAAAAAAAAAOZG5zLmdvb2dsZS5jb20NL2V4cGVyaW1lbnRhbA" - stamp, err := dnsstamps.NewServerStampFromString(stampStr) - - if err != nil || stamp.ProviderName == "" { - t.Fatalf("Could not parse stamp %s: %s", stampStr, err) - } - - log.Println(stampStr) - log.Printf("Proto=%s\n", stamp.Proto.String()) - log.Printf("ProviderName=%s\n", stamp.ProviderName) - log.Printf("Path=%s\n", stamp.Path) - log.Println("") - - // AdGuard DNSCrypt - stampStr = "sdns://AQIAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20" - stamp, err = dnsstamps.NewServerStampFromString(stampStr) - - if err != nil || stamp.ProviderName == "" { - t.Fatalf("Could not parse stamp %s: %s", stampStr, err) - } - - log.Println(stampStr) - log.Printf("Proto=%s\n", stamp.Proto.String()) - log.Printf("ProviderName=%s\n", stamp.ProviderName) - log.Printf("Path=%s\n", stamp.Path) - log.Printf("ServerAddrStr=%s\n", stamp.ServerAddrStr) - log.Printf("ServerPk len=%d\n", len(stamp.ServerPk)) - log.Println("") -} - -func TestInvalidStamp(t *testing.T) { - client := Client{} - _, _, err := client.Dial("sdns://AQIAAAAAAAAAFDE") - if err == nil { - t.Fatalf("Dial must not have been possible") - } -} - -func TestTimeoutOnDialError(t *testing.T) { - // AdGuard DNS pointing to a wrong IP - stampStr := "sdns://AQIAAAAAAAAADDguOC44Ljg6NTQ0MyDRK0fyUtzywrv4mRCG6vec5EldixbIoMQyLlLKPzkIcyIyLmRuc2NyeXB0LmRlZmF1bHQubnMxLmFkZ3VhcmQuY29t" - client := Client{Timeout: 300 * time.Millisecond} - - _, _, err := client.Dial(stampStr) - if err == nil { - t.Fatalf("Dial must not have been possible") - } - - if !os.IsTimeout(err) { - t.Fatalf("Not the timeout error") - } -} - -func TestTimeoutOnDialExchange(t *testing.T) { - // AdGuard DNS - stampStr := "sdns://AQIAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20" - client := Client{Timeout: 300 * time.Millisecond} - - serverInfo, _, err := client.Dial(stampStr) - if err != nil { - t.Fatalf("Could not establish connection with %s", stampStr) - } - - // Point it to an IP where there's no DNSCrypt server - serverInfo.ServerAddress = "8.8.8.8:5443" - req := dns.Msg{} - req.Id = dns.Id() - req.RecursionDesired = true - req.Question = []dns.Question{ - {Name: "google-public-dns-a.google.com.", Qtype: dns.TypeA, Qclass: dns.ClassINET}, - } - - // - _, _, err = client.Exchange(&req, serverInfo) - - if err == nil { - t.Fatalf("Exchange must not have been possible") - } - - if !os.IsTimeout(err) { - t.Fatalf("Not the timeout error") - } -} - -func TestDnsCryptResolver(t *testing.T) { - stamps := []struct { - stampStr string - }{ - { - // AdGuard DNS - stampStr: "sdns://AQIAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20", - }, - { - // AdGuard DNS Family - stampStr: "sdns://AQIAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMjo1NDQzILgxXdexS27jIKRw3C7Wsao5jMnlhvhdRUXWuMm1AFq6ITIuZG5zY3J5cHQuZmFtaWx5Lm5zMS5hZGd1YXJkLmNvbQ", - }, - { - // AdGuard DNS Unfiltered - stampStr: "sdns://AQIAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzNjo1NDQzILXoRNa4Oj4-EmjraB--pw3jxfpo29aIFB2_LsBmstr6JTIuZG5zY3J5cHQudW5maWx0ZXJlZC5uczEuYWRndWFyZC5jb20", - }, - { - // Cisco OpenDNS - stampStr: "sdns://AQAAAAAAAAAADjIwOC42Ny4yMjAuMjIwILc1EUAgbyJdPivYItf9aR6hwzzI1maNDL4Ev6vKQ_t5GzIuZG5zY3J5cHQtY2VydC5vcGVuZG5zLmNvbQ", - }, - { - // Cisco OpenDNS Family Shield - stampStr: "sdns://AQAAAAAAAAAADjIwOC42Ny4yMjAuMTIzILc1EUAgbyJdPivYItf9aR6hwzzI1maNDL4Ev6vKQ_t5GzIuZG5zY3J5cHQtY2VydC5vcGVuZG5zLmNvbQ", - }, - } - - for _, test := range stamps { - t.Run(test.stampStr, func(t *testing.T) { - checkDNSCryptServer(t, test.stampStr, "") - checkDNSCryptServer(t, test.stampStr, "tcp") - }) - } -} - -func checkDNSCryptServer(t *testing.T, stampStr string, proto string) { - client := Client{Proto: proto, Timeout: 10 * time.Second, AdjustPayloadSize: true} - serverInfo, rtt, err := client.Dial(stampStr) - if err != nil { - t.Fatalf("Could not establish connection with %s", stampStr) - } - - log.Printf("Established a connection with %s, ttl=%v, rtt=%v, proto=%s", serverInfo.ProviderName, time.Unix(int64(serverInfo.ServerCert.NotAfter), 0), rtt, proto) - req := dns.Msg{} - req.Id = dns.Id() - req.RecursionDesired = true - req.Question = []dns.Question{ - {Name: "google-public-dns-a.google.com.", Qtype: dns.TypeA, Qclass: dns.ClassINET}, - } - - reply, rtt, err := client.Exchange(&req, serverInfo) - if err != nil { - t.Fatalf("Couldn't talk to upstream %s: %s", serverInfo.ProviderName, err) - } - if len(reply.Answer) != 1 { - t.Fatalf("DNS upstream %s returned reply with wrong number of answers - %d", serverInfo.ProviderName, len(reply.Answer)) - } - if a, ok := reply.Answer[0].(*dns.A); ok { - if !net.IPv4(8, 8, 8, 8).Equal(a.A) { - t.Fatalf("DNS upstream %s returned wrong answer instead of 8.8.8.8: %v", serverInfo.ProviderName, a.A) - } - } else { - t.Fatalf("DNS upstream %s returned wrong answer type instead of A: %v", serverInfo.ProviderName, reply.Answer[0]) - } - log.Printf("Got proper response from %s, rtt=%v, proto=%s", serverInfo.ProviderName, rtt, proto) -} diff --git a/doc.go b/doc.go index 9c2c4d1..15e73dd 100644 --- a/doc.go +++ b/doc.go @@ -1,30 +1,64 @@ /* -Package dnscrypt implements a very simple DNSCrypt client library. +Package dnscrypt includes everything you need to work with DNSCrypt. You can run your own resolver, make DNS lookups to other DNSCrypt resolvers, and you can use it as a library in your own projects. -The idea is to let others use DNSCrypt resolvers in the same manner as we can use regular and DoT resolvers with miekg's DNS library. +Here's how to create a simple DNSCrypt client: -Here is a simple usage example: + // AdGuard DNS stamp + stampStr := "sdns://AQIAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20" - // AdGuard DNS stamp - stampStr := "sdns://AQIAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20" + // Initializing the DNSCrypt client + c := dnscrypt.Client{Net: "udp", Timeout: 10 * time.Second} - // Initializing the DNSCrypt client - c := dnscrypt.Client{Proto: "udp", Timeout: 10 * time.Second} + // Fetching and validating the server certificate + resolverInfo, err := client.Dial(stampStr) + if err != nil { + return err + } - // Fetching and validating the server certificate - serverInfo, rtt, err := client.Dial(stampStr) + // Create a DNS request + req := dns.Msg{} + req.Id = dns.Id() + req.RecursionDesired = true + req.Question = []dns.Question{ + {Name: "google-public-dns-a.google.com.", Qtype: dns.TypeA, Qclass: dns.ClassINET}, + } - // Create a DNS request - req := dns.Msg{} - req.Id = dns.Id() - req.RecursionDesired = true - req.Question = []dns.Question{ - {Name: "google-public-dns-a.google.com.", Qtype: dns.TypeA, Qclass: dns.ClassINET}, - } + // Get the DNS response + reply, err := c.Exchange(&req, resolverInfo) - // Get the DNS response - reply, rtt, err := c.Exchange(&req, serverInfo) +Here's how to run a DNSCrypt resolver: -Unfortunately, I have not found an easy way to use dnscrypt-proxy as a dependency so here's why this library was created. + // Prepare the test DNSCrypt server config + rc, err := dnscrypt.GenerateResolverConfig("example.org", nil) + if err != nil { + return err + } + + cert, err := rc.CreateCert() + if err != nil { + return err + } + + s := &dnscrypt.Server{ + ProviderName: rc.ProviderName, + ResolverCert: cert, + Handler: dnscrypt.DefaultHandler, + } + + // Prepare TCP listener + tcpConn, err := net.ListenTCP("tcp", &net.TCPAddr{IP: net.IPv4zero, Port: 443}) + if err != nil { + return err + } + + // Prepare UDP listener + udpConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 443}) + if err != nil { + return err + } + + // Start the server + go s.ServeUDP(udpConn) + go s.ServeTCP(tcpConn) */ package dnscrypt diff --git a/encrypted_query.go b/encrypted_query.go new file mode 100644 index 0000000..45ad28a --- /dev/null +++ b/encrypted_query.go @@ -0,0 +1,141 @@ +package dnscrypt + +import ( + "bytes" + "encoding/binary" + "math/rand" + "time" + + "github.com/ameshkov/dnscrypt/xsecretbox" + "golang.org/x/crypto/nacl/secretbox" +) + +// EncryptedQuery - a structure for encrypting and decrypting client queries +// +// ::= +// ::= AE( , ) +type EncryptedQuery struct { + // EsVersion - encryption to use + EsVersion CryptoConstruction + + // ClientMagic - a 8 byte identifier for the resolver certificate + // chosen by the client. + ClientMagic [clientMagicSize]byte + + // ClientPk - the client's public key + ClientPk [keySize]byte + + // With a 24 bytes nonce, a question sent by a DNSCrypt client must be + // encrypted using the shared secret, and a nonce constructed as follows: + // 12 bytes chosen by the client followed by 12 NUL (0) bytes. + // + // The client's half of the nonce can include a timestamp in addition to a + // counter or to random bytes, so that when a response is received, the + // client can use this timestamp to immediately discard responses to + // queries that have been sent too long ago, or dated in the future. + Nonce [nonceSize]byte +} + +// Encrypt - encrypts the specified DNS query, returns encrypted data ready to be sent. +// +// Note that this method will generate a random nonce automatically. +// +// The following fields must be set before calling this method: +// * EsVersion -- to encrypt the query +// * ClientMagic -- to send it with the query +// * ClientPk -- to send it with the query +func (q *EncryptedQuery) Encrypt(packet []byte, sharedKey [sharedKeySize]byte) ([]byte, error) { + var query []byte + + // Step 1: generate nonce + binary.BigEndian.PutUint64(q.Nonce[:8], uint64(time.Now().UnixNano())) + rand.Read(q.Nonce[8:12]) + + // Unencrypted part of the query: + // + query = append(query, q.ClientMagic[:]...) + query = append(query, q.ClientPk[:]...) + query = append(query, q.Nonce[:nonceSize/2]...) + + // + padded := pad(packet) + + // + nonce := q.Nonce + if q.EsVersion == XChacha20Poly1305 { + query = xsecretbox.Seal(query, nonce[:], padded, sharedKey[:]) + } else if q.EsVersion == XSalsa20Poly1305 { + var xsalsaNonce [nonceSize]byte + copy(xsalsaNonce[:], nonce[:]) + query = secretbox.Seal(query, padded, &xsalsaNonce, &sharedKey) + } else { + return nil, ErrEsVersion + } + + if len(query) > maxQueryLen { + return nil, ErrQueryTooLarge + } + + return query, nil +} + +// Decrypt - decrypts the client query, returns decrypted DNS packet. +// +// Please note, that before calling this method the following fields must be set: +// * ClientMagic -- to verify the query +// * EsVersion -- to decrypt +func (q *EncryptedQuery) Decrypt(query []byte, serverSecretKey [keySize]byte) ([]byte, error) { + headerLength := clientMagicSize + keySize + nonceSize/2 + if len(query) < headerLength+xsecretbox.TagSize+minDNSPacketSize { + return nil, ErrInvalidQuery + } + + // read and verify + clientMagic := [clientMagicSize]byte{} + copy(clientMagic[:], query[:clientMagicSize]) + if !bytes.Equal(clientMagic[:], q.ClientMagic[:]) { + return nil, ErrInvalidClientMagic + } + + // read + idx := clientMagicSize + copy(q.ClientPk[:keySize], query[idx:idx+keySize]) + + // generate server shared key + sharedKey, err := computeSharedKey(q.EsVersion, &serverSecretKey, &q.ClientPk) + if err != nil { + return nil, err + } + + // read + idx = idx + keySize + copy(q.Nonce[:nonceSize/2], query[idx:idx+nonceSize/2]) + + // read and decrypt + idx = idx + nonceSize/2 + encryptedQuery := query[idx:] + var packet []byte + if q.EsVersion == XChacha20Poly1305 { + packet, err = xsecretbox.Open(nil, q.Nonce[:], encryptedQuery, sharedKey[:]) + if err != nil { + return nil, ErrInvalidQuery + } + } else if q.EsVersion == XSalsa20Poly1305 { + var xsalsaServerNonce [24]byte + copy(xsalsaServerNonce[:], q.Nonce[:]) + var ok bool + packet, ok = secretbox.Open(nil, encryptedQuery, &xsalsaServerNonce, &sharedKey) + if !ok { + return nil, ErrInvalidQuery + } + } else { + return nil, ErrEsVersion + } + + packet, err = unpad(packet) + if err != nil { + return nil, ErrInvalidPadding + } + + return packet, nil +} diff --git a/encrypted_query_test.go b/encrypted_query_test.go new file mode 100644 index 0000000..d70643f --- /dev/null +++ b/encrypted_query_test.go @@ -0,0 +1,57 @@ +package dnscrypt + +import ( + "bytes" + "crypto/rand" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDNSCryptQueryEncryptDecryptXSalsa20Poly1305(t *testing.T) { + testDNSCryptQueryEncryptDecrypt(t, XSalsa20Poly1305) +} + +func TestDNSCryptQueryEncryptDecryptXChacha20Poly1305(t *testing.T) { + testDNSCryptQueryEncryptDecrypt(t, XChacha20Poly1305) +} + +func testDNSCryptQueryEncryptDecrypt(t *testing.T, esVersion CryptoConstruction) { + // Generate the secret/public pairs + clientSecretKey, clientPublicKey := generateRandomKeyPair() + serverSecretKey, serverPublicKey := generateRandomKeyPair() + + // Generate client shared key + clientSharedKey, err := computeSharedKey(esVersion, &clientSecretKey, &serverPublicKey) + assert.Nil(t, err) + + clientMagic := [clientMagicSize]byte{} + _, _ = rand.Read(clientMagic[:]) + + q1 := EncryptedQuery{ + EsVersion: esVersion, + ClientPk: clientPublicKey, + ClientMagic: clientMagic, + } + + // Generate random packet + packet := make([]byte, 100) + _, _ = rand.Read(packet[:]) + + // Encrypt it + encrypted, err := q1.Encrypt(packet, clientSharedKey) + assert.Nil(t, err) + + // Now let's try decrypting it + q2 := EncryptedQuery{ + EsVersion: esVersion, + ClientMagic: clientMagic, + } + + // Decrypt it + decrypted, err := q2.Decrypt(encrypted, serverSecretKey) + assert.Nil(t, err) + + // Check that packet is the same + assert.True(t, bytes.Equal(packet, decrypted)) +} diff --git a/encrypted_response.go b/encrypted_response.go new file mode 100644 index 0000000..b0972d0 --- /dev/null +++ b/encrypted_response.go @@ -0,0 +1,106 @@ +package dnscrypt + +import ( + "bytes" + "encoding/binary" + "math/rand" + "time" + + "github.com/ameshkov/dnscrypt/xsecretbox" + "golang.org/x/crypto/nacl/secretbox" +) + +// EncryptedResponse - structure for encrypting/decrypting server responses +// +// ::= +// ::= AE(, , ) +type EncryptedResponse struct { + // EsVersion - encryption to use + EsVersion CryptoConstruction + + // Nonce - ::= + // ::= the nonce sent by the client in the related query. + Nonce [nonceSize]byte +} + +// Encrypt - encrypts the server response +// +// EsVersion must be set. +// Nonce needs to be set to "client-nonce". +// This method will generate "resolver-nonce" and set it automatically. +func (r *EncryptedResponse) Encrypt(packet []byte, sharedKey [sharedKeySize]byte) ([]byte, error) { + var response []byte + + // Step 1: generate nonce + rand.Read(r.Nonce[12:16]) + binary.BigEndian.PutUint64(r.Nonce[16:nonceSize], uint64(time.Now().UnixNano())) + + // Unencrypted part of the query: + response = append(response, resolverMagic[:]...) + response = append(response, r.Nonce[:]...) + + // + padded := pad(packet) + + // + nonce := r.Nonce + if r.EsVersion == XChacha20Poly1305 { + response = xsecretbox.Seal(response, nonce[:], padded, sharedKey[:]) + } else if r.EsVersion == XSalsa20Poly1305 { + var xsalsaNonce [nonceSize]byte + copy(xsalsaNonce[:], nonce[:]) + response = secretbox.Seal(response, padded, &xsalsaNonce, &sharedKey) + } else { + return nil, ErrEsVersion + } + + return response, nil +} + +// Decrypt - decrypts the server response +// +// EsVersion must be set. +func (r *EncryptedResponse) Decrypt(response []byte, sharedKey [sharedKeySize]byte) ([]byte, error) { + headerLength := len(resolverMagic) + nonceSize + if len(response) < headerLength+xsecretbox.TagSize+minDNSPacketSize { + return nil, ErrInvalidResponse + } + + // read and verify + magic := [resolverMagicSize]byte{} + copy(magic[:], response[:resolverMagicSize]) + if !bytes.Equal(magic[:], resolverMagic[:]) { + return nil, ErrInvalidResolverMagic + } + + // read nonce + copy(r.Nonce[:], response[resolverMagicSize:nonceSize+resolverMagicSize]) + + // read and decrypt + encryptedResponse := response[nonceSize+resolverMagicSize:] + var packet []byte + var err error + if r.EsVersion == XChacha20Poly1305 { + packet, err = xsecretbox.Open(nil, r.Nonce[:], encryptedResponse, sharedKey[:]) + if err != nil { + return nil, ErrInvalidResponse + } + } else if r.EsVersion == XSalsa20Poly1305 { + var xsalsaServerNonce [24]byte + copy(xsalsaServerNonce[:], r.Nonce[:]) + var ok bool + packet, ok = secretbox.Open(nil, encryptedResponse, &xsalsaServerNonce, &sharedKey) + if !ok { + return nil, ErrInvalidResponse + } + } else { + return nil, ErrEsVersion + } + + packet, err = unpad(packet) + if err != nil { + return nil, ErrInvalidPadding + } + + return packet, nil +} diff --git a/encrypted_response_test.go b/encrypted_response_test.go new file mode 100644 index 0000000..1132435 --- /dev/null +++ b/encrypted_response_test.go @@ -0,0 +1,72 @@ +package dnscrypt + +import ( + "bytes" + "math/rand" + "testing" + + "github.com/ameshkov/dnscrypt/xsecretbox" + "github.com/stretchr/testify/assert" +) + +func TestDNSCryptResponseEncryptDecryptXSalsa20Poly1305(t *testing.T) { + testDNSCryptResponseEncryptDecrypt(t, XSalsa20Poly1305) +} + +func TestDNSCryptResponseEncryptDecryptXChacha20Poly1305(t *testing.T) { + testDNSCryptResponseEncryptDecrypt(t, XChacha20Poly1305) +} + +func testDNSCryptResponseEncryptDecrypt(t *testing.T, esVersion CryptoConstruction) { + // Generate the secret/public pairs + clientSecretKey, clientPublicKey := generateRandomKeyPair() + serverSecretKey, serverPublicKey := generateRandomKeyPair() + + // Generate client shared key + clientSharedKey, err := computeSharedKey(esVersion, &clientSecretKey, &serverPublicKey) + assert.Nil(t, err) + + // Generate server shared key + serverSharedKey, err := computeSharedKey(esVersion, &serverSecretKey, &clientPublicKey) + assert.Nil(t, err) + + r1 := &EncryptedResponse{ + EsVersion: esVersion, + } + // Fill client-nonce + _, _ = rand.Read(r1.Nonce[:nonceSize/12]) + + // Generate random packet + packet := make([]byte, 100) + _, _ = rand.Read(packet[:]) + + // Encrypt it + encrypted, err := r1.Encrypt(packet, serverSharedKey) + assert.Nil(t, err) + + // Now let's try decrypting it + r2 := &EncryptedResponse{ + EsVersion: esVersion, + } + + // Decrypt it + decrypted, err := r2.Decrypt(encrypted, clientSharedKey) + assert.Nil(t, err) + + // Check that packet is the same + assert.True(t, bytes.Equal(packet, decrypted)) + + // Now check invalid data (some random stuff) + _, err = r2.Decrypt(packet, clientSharedKey) + assert.NotNil(t, err) + + // Empty array + _, err = r2.Decrypt([]byte{}, clientSharedKey) + assert.NotNil(t, err) + + // Minimum valid size + b := make([]byte, len(resolverMagic)+nonceSize+xsecretbox.TagSize+minDNSPacketSize) + _, _ = rand.Read(b) + _, err = r2.Decrypt(b, clientSharedKey) + assert.NotNil(t, err) +} diff --git a/generate.go b/generate.go new file mode 100644 index 0000000..127e4cf --- /dev/null +++ b/generate.go @@ -0,0 +1,167 @@ +package dnscrypt + +import ( + "crypto/ed25519" + "crypto/rand" + "encoding/hex" + "strings" + "time" + + "github.com/AdguardTeam/golibs/log" + "github.com/ameshkov/dnsstamps" + "golang.org/x/crypto/curve25519" +) + +const dnsCryptV2Prefix = "2.dnscrypt-cert." + +// ResolverConfig - DNSCrypt resolver configuration +type ResolverConfig struct { + // DNSCrypt provider name + ProviderName string `yaml:"provider_name"` + + // PublicKey - DNSCrypt resolver public key + PublicKey string `yaml:"public_key"` + + // PrivateKey - DNSCrypt resolver private key + // The main and only purpose of this key is to sign the certificate + PrivateKey string `yaml:"private_key"` + + // ResolverSk - hex-encoded short-term private key. + // This key is used to encrypt/decrypt DNS queries. + // If not set, we'll generate a new random ResolverSk and ResolverPk. + ResolverSk string `yaml:"resolver_secret"` + + // ResolverSk - hex-encoded short-term public key corresponding to ResolverSk. + // This key is used to encrypt/decrypt DNS queries. + ResolverPk string `yaml:"resolver_public"` + + // EsVersion - crypto to use in this resolver + EsVersion CryptoConstruction `yaml:"es_version"` + + // CertificateTTL - time-to-live for the certificate that is generated using this ResolverConfig. + // If not set, we'll use 1 year by default. + CertificateTTL time.Duration `yaml:"certificate_ttl"` +} + +// CreateCert - generates a signed Cert to be used by Server +func (rc *ResolverConfig) CreateCert() (*Cert, error) { + log.Printf("Creating signed DNSCrypt certificate") + + notAfter := time.Now() + if rc.CertificateTTL > 0 { + notAfter = notAfter.Add(rc.CertificateTTL) + } else { + // Default cert validity is 1 year + notAfter = notAfter.Add(time.Hour * 24 * 365) + } + + cert := &Cert{ + Serial: uint32(time.Now().Unix()), + NotAfter: uint32(notAfter.Unix()), + NotBefore: uint32(time.Now().Unix()), + EsVersion: rc.EsVersion, + } + + // short-term public key + resolverPk, err := HexDecodeKey(rc.ResolverPk) + if err != nil { + return nil, err + } + // short-term private key + resolverSk, err := HexDecodeKey(rc.ResolverSk) + if err != nil { + return nil, err + } + + if len(resolverPk) != keySize || len(resolverSk) != keySize { + log.Printf("Short-term keys are not set, generating random ones") + sk, pk := generateRandomKeyPair() + resolverSk = sk[:] + resolverPk = pk[:] + } + + copy(cert.ResolverPk[:], resolverPk[:]) + copy(cert.ResolverSk[:], resolverSk) + + // private key + privateKey, err := HexDecodeKey(rc.PrivateKey) + if err != nil { + return nil, err + } + + // sign the data + cert.Sign(privateKey) + + log.Info("Signed cert: %s", cert.String()) + + // done + return cert, nil +} + +// CreateStamp - generates a DNS stamp for this resolver +func (rc *ResolverConfig) CreateStamp(addr string) (dnsstamps.ServerStamp, error) { + stamp := dnsstamps.ServerStamp{ + ProviderName: rc.ProviderName, + Proto: dnsstamps.StampProtoTypeDNSCrypt, + } + + serverPk, err := HexDecodeKey(rc.PublicKey) + if err != nil { + return stamp, err + } + + stamp.ServerPk = serverPk + stamp.ServerAddrStr = addr + return stamp, nil +} + +// GenerateResolverConfig - generates resolver configuration for a given provider name. +// providerName is mandatory. If needed, "2.dnscrypt-cert." prefix is added to it. +// privateKey is optional. If not set, it will be generated automatically. +func GenerateResolverConfig(providerName string, privateKey ed25519.PrivateKey) (ResolverConfig, error) { + rc := ResolverConfig{ + // Use XSalsa20Poly1305 by default + EsVersion: XSalsa20Poly1305, + } + if !strings.HasPrefix(providerName, dnsCryptV2Prefix) { + providerName = dnsCryptV2Prefix + providerName + } + rc.ProviderName = providerName + + var err error + if privateKey == nil { + // privateKey = gene + _, privateKey, err = ed25519.GenerateKey(rand.Reader) + if err != nil { + return rc, err + } + } + rc.PrivateKey = HexEncodeKey(privateKey) + rc.PublicKey = HexEncodeKey(privateKey.Public().(ed25519.PublicKey)) + + resolverSk, resolverPk := generateRandomKeyPair() + rc.ResolverSk = HexEncodeKey(resolverSk[:]) + rc.ResolverPk = HexEncodeKey(resolverPk[:]) + return rc, nil +} + +// HexEncodeKey - encodes a byte slice to a hex-encoded string. +func HexEncodeKey(b []byte) string { + return strings.ToUpper(hex.EncodeToString(b)) +} + +// HexDecodeKey - decodes a hex-encoded string with (optional) colons +// to a byte array. +func HexDecodeKey(str string) ([]byte, error) { + return hex.DecodeString(strings.ReplaceAll(str, ":", "")) +} + +// generateRandomKeyPair - generates a random key-pair +func generateRandomKeyPair() (privateKey [keySize]byte, publicKey [keySize]byte) { + privateKey = [keySize]byte{} + publicKey = [keySize]byte{} + + _, _ = rand.Read(privateKey[:]) + curve25519.ScalarBaseMult(&publicKey, &privateKey) + return +} diff --git a/generate_test.go b/generate_test.go new file mode 100644 index 0000000..6b46079 --- /dev/null +++ b/generate_test.go @@ -0,0 +1,38 @@ +package dnscrypt + +import ( + "bytes" + "crypto/ed25519" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestHexEncodeKey(t *testing.T) { + str := HexEncodeKey([]byte{1, 2, 3, 4}) + assert.Equal(t, "01020304", str) +} + +func TestHexDecodeKey(t *testing.T) { + b, err := HexDecodeKey("01:02:03:04") + assert.Nil(t, err) + assert.True(t, bytes.Equal(b, []byte{1, 2, 3, 4})) +} + +func TestGenerateResolverConfig(t *testing.T) { + rc, err := GenerateResolverConfig("example.org", nil) + assert.Nil(t, err) + assert.Equal(t, "2.dnscrypt-cert.example.org", rc.ProviderName) + assert.Equal(t, ed25519.PrivateKeySize*2, len(rc.PrivateKey)) + assert.Equal(t, keySize*2, len(rc.ResolverSk)) + assert.Equal(t, keySize*2, len(rc.ResolverPk)) + + cert, err := rc.CreateCert() + assert.Nil(t, err) + + assert.True(t, cert.VerifyDate()) + + publicKey, err := HexDecodeKey(rc.PublicKey) + assert.Nil(t, err) + assert.True(t, cert.VerifySignature(publicKey)) +} diff --git a/go.mod b/go.mod index 5119e62..a4713b8 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,17 @@ module github.com/ameshkov/dnscrypt require ( + github.com/AdguardTeam/golibs v0.4.2 github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 github.com/ameshkov/dnsstamps v1.0.1 + github.com/jessevdk/go-flags v1.4.0 github.com/miekg/dns v1.1.29 + github.com/stretchr/testify v1.6.1 golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 - golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e // indirect + golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e golang.org/x/sys v0.0.0-20200331124033-c3d80250170d // indirect + gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c ) go 1.14 diff --git a/go.sum b/go.sum index d8b4c56..c07da94 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,25 @@ +github.com/AdguardTeam/golibs v0.4.2 h1:7M28oTZFoFwNmp8eGPb3ImmYbxGaJLyQXeIFVHjME0o= +github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw= github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us= github.com/ameshkov/dnsstamps v1.0.1 h1:LhGvgWDzhNJh+kBQd/AfUlq1vfVe109huiXw4JhnPug= github.com/ameshkov/dnsstamps v1.0.1/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/miekg/dns v1.1.29 h1:xHBEhR+t5RzcFJjBLJlax2daXOrTYtr9z4WdKEfWFzg= github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 h1:3zb4D3T4G8jdExgVU/95+vQXfpEPiMdCaZgmGVxjNHM= @@ -27,3 +41,9 @@ golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/handler.go b/handler.go new file mode 100644 index 0000000..eb3430f --- /dev/null +++ b/handler.go @@ -0,0 +1,60 @@ +package dnscrypt + +import ( + "net" + "time" + + "github.com/miekg/dns" +) + +const defaultTimeout = 10 * time.Second + +// Handler is implemented by any value that implements ServeDNS. +type Handler interface { + ServeDNS(rw ResponseWriter, r *dns.Msg) error +} + +// ResponseWriter - interface that needs to be implemented for different protocols +type ResponseWriter interface { + LocalAddr() net.Addr // LocalAddr - local socket address + RemoteAddr() net.Addr // RemoteAddr - remote client socket address + WriteMsg(m *dns.Msg) error // WriteMsg - writes response message to the client +} + +// DefaultHandler - default Handler implementation +// that is used by Server if custom handler is not configured +var DefaultHandler Handler = &defaultHandler{ + udpClient: &dns.Client{ + Net: "udp", + Timeout: defaultTimeout, + }, + tcpClient: &dns.Client{ + Net: "tcp", + Timeout: defaultTimeout, + }, + addr: "94.140.14.140:53", +} + +type defaultHandler struct { + udpClient *dns.Client + tcpClient *dns.Client + addr string +} + +// ServeDNS - implements Handler interface +func (h *defaultHandler) ServeDNS(rw ResponseWriter, r *dns.Msg) error { + // Google DNS + res, _, err := h.udpClient.Exchange(r, h.addr) + if err != nil { + return err + } + + if res.Truncated { + res, _, err = h.tcpClient.Exchange(r, h.addr) + if err != nil { + return err + } + } + + return rw.WriteMsg(res) +} diff --git a/server.go b/server.go new file mode 100644 index 0000000..0e08557 --- /dev/null +++ b/server.go @@ -0,0 +1,141 @@ +package dnscrypt + +import ( + "github.com/AdguardTeam/golibs/log" + "github.com/miekg/dns" +) + +// Server - a simple DNSCrypt server implementation +type Server struct { + // ProviderName - DNSCrypt provider name + ProviderName string + + // ResolverCert - contains resolver certificate. + ResolverCert *Cert + + // Handler to invoke. If nil, uses DefaultHandler. + Handler Handler +} + +// serveDNS - serves DNS response +func (s *Server) serveDNS(rw ResponseWriter, r *dns.Msg) { + if r == nil || len(r.Question) != 1 || r.Response { + log.Tracef("Invalid query: %v", r) + return + } + + log.Tracef("Handling a DNS query: %s", r.Question[0].Name) + + handler := s.Handler + if handler == nil { + handler = DefaultHandler + } + + err := handler.ServeDNS(rw, r) + if err != nil { + log.Tracef("Error while handing a DNS query: %v", err) + + reply := &dns.Msg{} + reply.SetRcode(r, dns.RcodeServerFailure) + _ = rw.WriteMsg(reply) + } +} + +// encrypt - encrypts DNSCrypt response +func (s *Server) encrypt(m *dns.Msg, q EncryptedQuery) ([]byte, error) { + r := EncryptedResponse{ + EsVersion: q.EsVersion, + Nonce: q.Nonce, + } + packet, err := m.Pack() + if err != nil { + return nil, err + } + + sharedKey, err := computeSharedKey(q.EsVersion, &s.ResolverCert.ResolverSk, &q.ClientPk) + if err != nil { + return nil, err + } + + return r.Encrypt(packet, sharedKey) +} + +// decrypt - decrypts the incoming message and returns a DNS message to process +func (s *Server) decrypt(b []byte) (*dns.Msg, EncryptedQuery, error) { + q := EncryptedQuery{ + EsVersion: s.ResolverCert.EsVersion, + ClientMagic: s.ResolverCert.ClientMagic, + } + msg, err := q.Decrypt(b, s.ResolverCert.ResolverSk) + if err != nil { + // Failed to decrypt, dropping it + return nil, q, err + } + + r := new(dns.Msg) + err = r.Unpack(msg) + if err != nil { + // Invalid DNS message, ignore + return nil, q, err + } + + return r, q, nil +} + +// handleHandshake - handles a TXT request that requests certificate data +func (s *Server) handleHandshake(b []byte, certTxt string) ([]byte, error) { + m := new(dns.Msg) + err := m.Unpack(b) + if err != nil { + // Not a handshake, just ignore it + return nil, err + } + + if len(m.Question) != 1 || m.Response { + // Invalid query + return nil, ErrInvalidQuery + } + + q := m.Question[0] + providerName := dns.Fqdn(s.ProviderName) + if q.Qtype != dns.TypeTXT || q.Name != providerName { + // Invalid provider name or type, doing nothing + return nil, ErrInvalidQuery + } + + reply := new(dns.Msg) + reply.SetReply(m) + txt := &dns.TXT{ + Hdr: dns.RR_Header{ + Name: q.Name, + Rrtype: dns.TypeTXT, + Ttl: 60, // use 60 seconds by default, but it shouldn't matter + Class: dns.ClassINET, + }, + Txt: []string{ + certTxt, + }, + } + reply.Answer = append(reply.Answer, txt) + return reply.Pack() +} + +// validate - checks if the Server config is properly set +func (s *Server) validate() bool { + if s.ResolverCert == nil { + log.Error("ResolverCert must be set") + return false + } + + if !s.ResolverCert.VerifyDate() { + log.Error("ResolverCert date is not valid") + return false + } + + if s.ProviderName == "" { + log.Error("ProviderName must be set") + return false + } + + return true +} diff --git a/server_tcp.go b/server_tcp.go new file mode 100644 index 0000000..4913def --- /dev/null +++ b/server_tcp.go @@ -0,0 +1,122 @@ +package dnscrypt + +import ( + "bytes" + "net" + + "github.com/AdguardTeam/golibs/log" + "github.com/miekg/dns" +) + +// TCPResponseWriter - ResponseWriter implementation for TCP +type TCPResponseWriter struct { + tcpConn net.Conn + encrypt encryptionFunc + req *dns.Msg + query EncryptedQuery +} + +// type check +var _ ResponseWriter = &TCPResponseWriter{} + +// LocalAddr - server socket local address +func (w *TCPResponseWriter) LocalAddr() net.Addr { + return w.tcpConn.LocalAddr() +} + +// RemoteAddr - client's address +func (w *TCPResponseWriter) RemoteAddr() net.Addr { + return w.tcpConn.RemoteAddr() +} + +// WriteMsg - writes DNS message to the client +func (w *TCPResponseWriter) WriteMsg(m *dns.Msg) error { + m.Truncate(dnsSize("tcp", w.req)) + + res, err := w.encrypt(m, w.query) + if err != nil { + log.Tracef("Failed to encrypt the DNS query: %v", err) + return err + } + + return writePrefixed(res, w.tcpConn) +} + +// ServeTCP - listens to TCP connections, queries are then processed by Server.Handler. +// It blocks the calling goroutine and to stop it you need to close the listener. +func (s *Server) ServeTCP(l net.Listener) error { + // Check that server is properly configured + if !s.validate() { + return ErrServerConfig + } + + // Serialize the cert right away and prepare it to be sent to the client + certBuf, err := s.ResolverCert.Serialize() + if err != nil { + return err + } + certTxt := packTxtString(certBuf) + + log.Info("Entering DNSCrypt TCP listening loop tcp://%s", l.Addr().String()) + + for { + conn, err := l.Accept() + if err == nil { + go func() { + _ = s.handleTCPConnection(conn, certTxt) + _ = conn.Close() + }() + } + + if err != nil { + if isConnClosed(err) { + log.Info("udpListen.ReadFrom() returned because we're reading from a closed connection, exiting loop") + } else { + log.Info("got error when reading from UDP listen: %s", err) + } + break + } + } + + return nil +} + +func (s *Server) handleTCPConnection(conn net.Conn, certTxt string) error { + for { + b, err := readPrefixed(conn) + if err != nil { + return err + } + if len(b) < minDNSPacketSize { + // Ignore the packets that are too short + return ErrTooShort + } + + if bytes.Equal(b[:clientMagicSize], s.ResolverCert.ClientMagic[:]) { + // This is an encrypted message, we should decrypt it + m, q, err := s.decrypt(b) + if err != nil { + log.Tracef("failed to decrypt incoming message: %v", err) + return err + } + rw := &TCPResponseWriter{ + tcpConn: conn, + encrypt: s.encrypt, + req: m, + query: q, + } + s.serveDNS(rw, m) + } else { + // Most likely this a DNS message requesting the certificate + reply, err := s.handleHandshake(b, certTxt) + if err != nil { + log.Tracef("Failed to process a plain DNS query: %v", err) + return err + } + err = writePrefixed(reply, conn) + if err != nil { + return err + } + } + } +} diff --git a/server_test.go b/server_test.go new file mode 100644 index 0000000..f981925 --- /dev/null +++ b/server_test.go @@ -0,0 +1,169 @@ +package dnscrypt + +import ( + "bytes" + "crypto/ed25519" + "fmt" + "net" + "testing" + "time" + + "github.com/ameshkov/dnsstamps" + "github.com/miekg/dns" + "github.com/stretchr/testify/assert" +) + +func TestServerUDPServeCert(t *testing.T) { + testServerServeCert(t, "udp") +} + +func TestServerTCPServeCert(t *testing.T) { + testServerServeCert(t, "tcp") +} + +func TestServerUDPRespondMessages(t *testing.T) { + testServerRespondMessages(t, "udp") +} + +func TestServerTCPRespondMessages(t *testing.T) { + testServerRespondMessages(t, "tcp") +} + +func testServerServeCert(t *testing.T, network string) { + srv := newTestServer(t, &testHandler{}) + defer srv.Close() + + client := &Client{ + Net: network, + Timeout: 1 * time.Second, + } + + serverAddr := fmt.Sprintf("127.0.0.1:%d", srv.UDPAddr().Port) + if network == "tcp" { + serverAddr = fmt.Sprintf("127.0.0.1:%d", srv.TCPAddr().Port) + } + + stamp := dnsstamps.ServerStamp{ + ServerAddrStr: serverAddr, + ServerPk: srv.resolverPk, + ProviderName: srv.server.ProviderName, + Proto: dnsstamps.StampProtoTypeDNSCrypt, + } + ri, err := client.DialStamp(stamp) + assert.Nil(t, err) + assert.NotNil(t, ri) + + assert.Equal(t, ri.ProviderName, srv.server.ProviderName) + assert.True(t, bytes.Equal(srv.server.ResolverCert.ClientMagic[:], ri.ResolverCert.ClientMagic[:])) + assert.Equal(t, srv.server.ResolverCert.EsVersion, ri.ResolverCert.EsVersion) + assert.Equal(t, srv.server.ResolverCert.Signature, ri.ResolverCert.Signature) + assert.Equal(t, srv.server.ResolverCert.NotBefore, ri.ResolverCert.NotBefore) + assert.Equal(t, srv.server.ResolverCert.NotAfter, ri.ResolverCert.NotAfter) + assert.True(t, bytes.Equal(srv.server.ResolverCert.ResolverPk[:], ri.ResolverCert.ResolverPk[:])) + assert.True(t, bytes.Equal(srv.server.ResolverCert.ResolverPk[:], ri.ResolverCert.ResolverPk[:])) +} + +func testServerRespondMessages(t *testing.T, network string) { + srv := newTestServer(t, &testHandler{}) + defer srv.Close() + + client := &Client{ + Timeout: 1 * time.Second, + Net: network, + } + + serverAddr := fmt.Sprintf("127.0.0.1:%d", srv.UDPAddr().Port) + if network == "tcp" { + serverAddr = fmt.Sprintf("127.0.0.1:%d", srv.TCPAddr().Port) + } + + stamp := dnsstamps.ServerStamp{ + ServerAddrStr: serverAddr, + ServerPk: srv.resolverPk, + ProviderName: srv.server.ProviderName, + Proto: dnsstamps.StampProtoTypeDNSCrypt, + } + ri, err := client.DialStamp(stamp) + assert.Nil(t, err) + assert.NotNil(t, ri) + + conn, err := net.Dial(network, stamp.ServerAddrStr) + assert.Nil(t, err) + + for i := 0; i < 10; i++ { + m := createTestMessage() + res, err := client.ExchangeConn(conn, m, ri) + assert.Nil(t, err) + assertTestMessageResponse(t, res) + } +} + +type testServer struct { + server *Server + resolverPk ed25519.PublicKey + udpConn *net.UDPConn + tcpListen net.Listener + handler Handler +} + +func (s *testServer) TCPAddr() *net.TCPAddr { + return s.tcpListen.Addr().(*net.TCPAddr) +} + +func (s *testServer) UDPAddr() *net.UDPAddr { + return s.udpConn.LocalAddr().(*net.UDPAddr) +} + +func (s *testServer) Close() { + _ = s.udpConn.Close() + _ = s.tcpListen.Close() +} + +func newTestServer(t *testing.T, handler Handler) *testServer { + rc, err := GenerateResolverConfig("example.org", nil) + assert.Nil(t, err) + cert, err := rc.CreateCert() + assert.Nil(t, err) + + s := &Server{ + ProviderName: rc.ProviderName, + ResolverCert: cert, + Handler: handler, + } + + privateKey, err := HexDecodeKey(rc.PrivateKey) + assert.Nil(t, err) + publicKey := ed25519.PrivateKey(privateKey).Public().(ed25519.PublicKey) + srv := &testServer{ + server: s, + resolverPk: publicKey, + } + + srv.tcpListen, err = net.ListenTCP("tcp", &net.TCPAddr{IP: net.IPv4zero, Port: 0}) + assert.Nil(t, err) + srv.udpConn, err = net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 0}) + assert.Nil(t, err) + + go s.ServeUDP(srv.udpConn) + go s.ServeTCP(srv.tcpListen) + return srv +} + +type testHandler struct{} + +// ServeDNS - implements Handler interface +func (h *testHandler) ServeDNS(rw ResponseWriter, r *dns.Msg) error { + // Google DNS + res := new(dns.Msg) + res.SetReply(r) + answer := new(dns.A) + answer.Hdr = dns.RR_Header{ + Name: r.Question[0].Name, + Rrtype: dns.TypeA, + Ttl: 300, + Class: dns.ClassINET, + } + answer.A = net.IPv4(8, 8, 8, 8) + res.Answer = append(res.Answer, answer) + return rw.WriteMsg(res) +} diff --git a/server_udp.go b/server_udp.go new file mode 100644 index 0000000..bb467a5 --- /dev/null +++ b/server_udp.go @@ -0,0 +1,124 @@ +package dnscrypt + +import ( + "bytes" + "net" + + "github.com/AdguardTeam/golibs/log" + "github.com/miekg/dns" +) + +type encryptionFunc func(m *dns.Msg, q EncryptedQuery) ([]byte, error) + +// UDPResponseWriter - ResponseWriter implementation for UDP +type UDPResponseWriter struct { + udpConn *net.UDPConn // UDP connection + remoteAddr *net.UDPAddr // Remote peer address + localIP net.IP // Local IP (that was used to accept the remote connection) + encrypt encryptionFunc // DNSCRypt encryption function + req *dns.Msg // DNS query that was processed + query EncryptedQuery // DNSCrypt query properties +} + +// type check +var _ ResponseWriter = &UDPResponseWriter{} + +// LocalAddr - server socket local address +func (w *UDPResponseWriter) LocalAddr() net.Addr { + return w.udpConn.LocalAddr() +} + +// RemoteAddr - client's address +func (w *UDPResponseWriter) RemoteAddr() net.Addr { + return w.remoteAddr +} + +// WriteMsg - writes DNS message to the client +func (w *UDPResponseWriter) WriteMsg(m *dns.Msg) error { + m.Truncate(dnsSize("udp", w.req)) + + res, err := w.encrypt(m, w.query) + if err != nil { + log.Tracef("Failed to encrypt the DNS query: %v", err) + return err + } + + _, _ = udpWrite(res, w.udpConn, w.remoteAddr, w.localIP) + return nil +} + +// ServeUDP - listens to UDP connections, queries are then processed by Server.Handler. +// It blocks the calling goroutine and to stop it you need to close the listener. +func (s *Server) ServeUDP(l *net.UDPConn) error { + // Check that server is properly configured + if !s.validate() { + return ErrServerConfig + } + + // set UDP options to allow receiving OOB data + err := udpSetOptions(l) + if err != nil { + return err + } + + // Buffer to read incoming messages + b := make([]byte, dns.MaxMsgSize) + + // Serialize the cert right away and prepare it to be sent to the client + certBuf, err := s.ResolverCert.Serialize() + if err != nil { + return err + } + certTxt := packTxtString(certBuf) + + // Init oobSize - it will be used later when reading and writing UDP messages + oobSize := udpGetOOBSize() + + log.Info("Entering DNSCrypt UDP listening loop on udp://%s", l.LocalAddr().String()) + + for { + n, localIP, addr, err := udpRead(l, b, oobSize) + if n < minDNSPacketSize { + // Ignore the packets that are too short + continue + } + + if bytes.Equal(b[:clientMagicSize], s.ResolverCert.ClientMagic[:]) { + // This is an encrypted message, we should decrypt it + m, q, err := s.decrypt(b[:n]) + if err == nil { + rw := &UDPResponseWriter{ + udpConn: l, + remoteAddr: addr, + localIP: localIP, + encrypt: s.encrypt, + req: m, + query: q, + } + go s.serveDNS(rw, m) + } else { + log.Tracef("Failed to decrypt incoming message len=%d: %v", n, err) + } + } else { + // Most likely this a DNS message requesting the certificate + reply, err := s.handleHandshake(b, certTxt) + if err != nil { + log.Tracef("Failed to process a plain DNS query: %v", err) + } + if err == nil { + _, _ = l.WriteTo(reply, addr) + } + } + + if err != nil { + if isConnClosed(err) { + log.Info("udpListen.ReadFrom() returned because we're reading from a closed connection, exiting loop") + } else { + log.Info("got error when reading from UDP listen: %s", err) + } + break + } + } + + return nil +} diff --git a/udp_unix.go b/udp_unix.go new file mode 100644 index 0000000..8d1f50c --- /dev/null +++ b/udp_unix.go @@ -0,0 +1,83 @@ +// +build aix darwin dragonfly linux netbsd openbsd solaris freebsd + +package dnscrypt + +import ( + "fmt" + "net" + + "golang.org/x/net/ipv4" + "golang.org/x/net/ipv6" +) + +// udpGetOOBSize - get max. size of received OOB data +// It will then be used in the ReadMsgUDP function +func udpGetOOBSize() int { + oob4 := ipv4.NewControlMessage(ipv4.FlagDst | ipv4.FlagInterface) + oob6 := ipv6.NewControlMessage(ipv6.FlagDst | ipv6.FlagInterface) + + if len(oob4) > len(oob6) { + return len(oob4) + } + return len(oob6) +} + +// udpSetOptions - set options on a UDP socket to be able to receive the necessary OOB data +func udpSetOptions(c *net.UDPConn) error { + err6 := ipv6.NewPacketConn(c).SetControlMessage(ipv6.FlagDst|ipv6.FlagInterface, true) + err4 := ipv4.NewPacketConn(c).SetControlMessage(ipv4.FlagDst|ipv4.FlagInterface, true) + if err6 != nil && err4 != nil { + return fmt.Errorf("failed to call SetControlMessage: ipv4: %v ipv6: %v", err4, err6) + } + return nil +} + +// udpRead - receive payload and OOB data from the UDP socket +func udpRead(c *net.UDPConn, buf []byte, udpOOBSize int) (int, net.IP, *net.UDPAddr, error) { + var oobn int + oob := make([]byte, udpOOBSize) + var err error + var n int + var remoteAddr *net.UDPAddr + n, oobn, _, remoteAddr, err = c.ReadMsgUDP(buf, oob) + if err != nil { + return -1, nil, nil, err + } + + localIP := udpGetDstFromOOB(oob[:oobn]) + return n, localIP, remoteAddr, nil +} + +// udpWrite - writes to the UDP socket and sets local IP to OOB data +func udpWrite(bytes []byte, conn *net.UDPConn, remoteAddr *net.UDPAddr, localIP net.IP) (int, error) { + n, _, err := conn.WriteMsgUDP(bytes, udpMakeOOBWithSrc(localIP), remoteAddr) + return n, err +} + +// udpGetDstFromOOB - get destination IP from OOB data +func udpGetDstFromOOB(oob []byte) net.IP { + cm6 := &ipv6.ControlMessage{} + if cm6.Parse(oob) == nil && cm6.Dst != nil { + return cm6.Dst + } + + cm4 := &ipv4.ControlMessage{} + if cm4.Parse(oob) == nil && cm4.Dst != nil { + return cm4.Dst + } + + return nil +} + +// udpMakeOOBWithSrc - make OOB data with a specified source IP +func udpMakeOOBWithSrc(ip net.IP) []byte { + if ip.To4() == nil { + cm := &ipv6.ControlMessage{} + cm.Src = ip + return cm.Marshal() + } + + cm := &ipv4.ControlMessage{} + cm.Src = ip + return cm.Marshal() +} diff --git a/udp_windows.go b/udp_windows.go new file mode 100644 index 0000000..74ac910 --- /dev/null +++ b/udp_windows.go @@ -0,0 +1,30 @@ +package dnscrypt + +import "net" + +// udpGetOOBSize - get max. size of received OOB data +// Does nothing on Windows +func udpGetOOBSize() int { + return 0 +} + +// udpSetOptions - set options on a UDP socket to be able to receive the necessary OOB data +// Does nothing on Windows +func udpSetOptions(c *net.UDPConn) error { + return nil +} + +// udpRead - receive payload from the UDP socket +func udpRead(c *net.UDPConn, buf []byte, _ int) (int, net.IP, *net.UDPAddr, error) { + n, addr, err := c.ReadFrom(buf) + var udpAddr *net.UDPAddr + if addr != nil { + udpAddr = addr.(*net.UDPAddr) + } + return n, nil, udpAddr, err +} + +// udpWrite - writes to the UDP socket +func udpWrite(bytes []byte, conn *net.UDPConn, remoteAddr *net.UDPAddr, _ net.IP) (int, error) { + return conn.WriteTo(bytes, remoteAddr) +} diff --git a/util.go b/util.go new file mode 100644 index 0000000..72b654a --- /dev/null +++ b/util.go @@ -0,0 +1,251 @@ +package dnscrypt + +import ( + "encoding/binary" + "io" + "net" + "strings" + + "github.com/ameshkov/dnscrypt/xsecretbox" + "github.com/miekg/dns" + "golang.org/x/crypto/nacl/box" +) + +// Prior to encryption, queries are padded using the ISO/IEC 7816-4 +// format. The padding starts with a byte valued 0x80 followed by a +// variable number of NUL bytes. +// +// ## Padding for client queries over UDP +// +// must be at least +// bytes. If the length of the client query is less than , +// the padding length must be adjusted in order to satisfy this +// requirement. +// +// is a variable length, initially set to 256 bytes, and +// must be a multiple of 64 bytes. +// +// ## Padding for client queries over TCP +// +// The length of is randomly chosen between 1 and 256 +// bytes (including the leading 0x80), but the total length of +// must be a multiple of 64 bytes. +// +// For example, an originally unpadded 56-bytes DNS query can be padded as: +// +// <56-bytes-query> 0x80 0x00 0x00 0x00 0x00 0x00 0x00 0x00 +// or +// <56-bytes-query> 0x80 (0x00 * 71) +// or +// <56-bytes-query> 0x80 (0x00 * 135) +// or +// <56-bytes-query> 0x80 (0x00 * 199) +func pad(packet []byte) []byte { + // get closest divisible by 64 to + 1 byte for 0x80 + minQuestionSize := (len(packet)+1+63)/64 + 64 + + // padded size can't be less than minUDPQuestionSize + minQuestionSize = max(minUDPQuestionSize, minQuestionSize) + + packet = append(packet, 0x80) + for len(packet) < minQuestionSize { + packet = append(packet, 0) + } + + return packet +} + +// unpad - removes padding bytes +func unpad(packet []byte) ([]byte, error) { + for i := len(packet); ; { + if i == 0 { + return nil, ErrInvalidPadding + } + i-- + if packet[i] == 0x80 { + if i < minDNSPacketSize { + return nil, ErrInvalidPadding + } + + return packet[:i], nil + } else if packet[i] != 0x00 { + return nil, ErrInvalidPadding + } + } +} + +// computeSharedKey - computes a shared key +func computeSharedKey(cryptoConstruction CryptoConstruction, secretKey *[keySize]byte, publicKey *[keySize]byte) ([keySize]byte, error) { + if cryptoConstruction == XChacha20Poly1305 { + sharedKey, err := xsecretbox.SharedKey(*secretKey, *publicKey) + if err != nil { + return sharedKey, err + } + return sharedKey, nil + } else if cryptoConstruction == XSalsa20Poly1305 { + sharedKey := [sharedKeySize]byte{} + box.Precompute(&sharedKey, publicKey, secretKey) + return sharedKey, nil + } + return [keySize]byte{}, ErrEsVersion +} + +func max(a, b int) int { + if a > b { + return a + } + return b +} + +func isDigit(b byte) bool { return b >= '0' && b <= '9' } + +func dddToByte(s []byte) byte { + return (s[0]-'0')*100 + (s[1]-'0')*10 + (s[2] - '0') +} + +const ( + escapedByteSmall = "" + + `\000\001\002\003\004\005\006\007\008\009` + + `\010\011\012\013\014\015\016\017\018\019` + + `\020\021\022\023\024\025\026\027\028\029` + + `\030\031` + escapedByteLarge = `\127\128\129` + + `\130\131\132\133\134\135\136\137\138\139` + + `\140\141\142\143\144\145\146\147\148\149` + + `\150\151\152\153\154\155\156\157\158\159` + + `\160\161\162\163\164\165\166\167\168\169` + + `\170\171\172\173\174\175\176\177\178\179` + + `\180\181\182\183\184\185\186\187\188\189` + + `\190\191\192\193\194\195\196\197\198\199` + + `\200\201\202\203\204\205\206\207\208\209` + + `\210\211\212\213\214\215\216\217\218\219` + + `\220\221\222\223\224\225\226\227\228\229` + + `\230\231\232\233\234\235\236\237\238\239` + + `\240\241\242\243\244\245\246\247\248\249` + + `\250\251\252\253\254\255` +) + +// escapeByte returns the \DDD escaping of b which must +// satisfy b < ' ' || b > '~'. +func escapeByte(b byte) string { + if b < ' ' { + return escapedByteSmall[b*4 : b*4+4] + } + + b -= '~' + 1 + // The cast here is needed as b*4 may overflow byte. + return escapedByteLarge[int(b)*4 : int(b)*4+4] +} + +func packTxtString(buf []byte) string { + var out strings.Builder + out.Grow(3 + len(buf)) + for i := 0; i < len(buf); i++ { + b := buf[i] + switch { + case b == '"' || b == '\\': + out.WriteByte('\\') + out.WriteByte(b) + case b < ' ' || b > '~': + out.WriteString(escapeByte(b)) + default: + out.WriteByte(b) + } + } + return out.String() +} + +func unpackTxtString(s string) ([]byte, error) { + bs := make([]byte, len(s)) + msg := make([]byte, 0) + copy(bs, s) + for i := 0; i < len(bs); i++ { + if bs[i] == '\\' { + i++ + if i == len(bs) { + break + } + if i+2 < len(bs) && isDigit(bs[i]) && isDigit(bs[i+1]) && isDigit(bs[i+2]) { + msg = append(msg, dddToByte(bs[i:])) + i += 2 + } else if bs[i] == 't' { + msg = append(msg, '\t') + } else if bs[i] == 'r' { + msg = append(msg, '\r') + } else if bs[i] == 'n' { + msg = append(msg, '\n') + } else { + msg = append(msg, bs[i]) + } + } else { + msg = append(msg, bs[i]) + } + } + return msg, nil +} + +// dnsSize returns if buffer size *advertised* in the requests OPT record. +// Or when the request was over TCP, we return the maximum allowed size of 64K. +func dnsSize(proto string, r *dns.Msg) int { + size := uint16(0) + if o := r.IsEdns0(); o != nil { + size = o.UDPSize() + } + + if proto != "udp" { + return dns.MaxMsgSize + } + + if size < dns.MinMsgSize { + return dns.MinMsgSize + } + + // normalize size + return int(size) +} + +// readPrefixed -- reads a DNS message with a 2-byte prefix containing message length +func readPrefixed(conn net.Conn) ([]byte, error) { + l := make([]byte, 2) + _, err := conn.Read(l) + if err != nil { + return nil, err + } + packetLen := binary.BigEndian.Uint16(l) + if packetLen > dns.MaxMsgSize { + return nil, ErrQueryTooLarge + } + + buf := make([]byte, packetLen) + _, err = io.ReadFull(conn, buf) + if err != nil { + return nil, err + } + return buf, nil +} + +// writePrefixed -- write a DNS message to a TCP connection +// it first writes a 2-byte prefix followed by the message itself +func writePrefixed(b []byte, conn net.Conn) error { + l := make([]byte, 2) + binary.BigEndian.PutUint16(l, uint16(len(b))) + _, err := (&net.Buffers{l, b}).WriteTo(conn) + return err +} + +// isConnClosed - checks if the error signals of a closed server connecting +func isConnClosed(err error) bool { + if err == nil { + return false + } + nerr, ok := err.(*net.OpError) + if !ok { + return false + } + + if strings.Contains(nerr.Err.Error(), "use of closed network connection") { + return true + } + + return false +} From 2789624e349f6fdf173797a33d7fe0ea3ed5c6f0 Mon Sep 17 00:00:00 2001 From: Andrey Meshkov Date: Mon, 19 Oct 2020 17:26:57 +0300 Subject: [PATCH 2/3] Improved instruction --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 2b2c8e8..5c98570 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,8 @@ +[![Build Status](https://travis-ci.com/ameshkov/dnscrypt.svg?branch=master)](https://travis-ci.com/ameshkov/dnscrypt) +[![Code Coverage](https://img.shields.io/codecov/c/github/ameshkov/dnscrypt/master.svg)](https://codecov.io/github/ameshkov/dnscrypt?branch=master) +[![Go Report Card](https://goreportcard.com/badge/github.com/ameshkov/dnscrypt)](https://goreportcard.com/report/ameshkov/dnscrypt) +[![Go Doc](https://godoc.org/github.com/ameshkov/dnscrypt?status.svg)](https://godoc.org/github.com/ameshkov/dnscrypt) + # DNSCrypt Go Golang-implementation of the [DNSCrypt v2 protocol](https://dnscrypt.info/protocol). @@ -5,6 +10,7 @@ Golang-implementation of the [DNSCrypt v2 protocol](https://dnscrypt.info/protoc This repo includes everything you need to work with DNSCrypt. You can run your own resolver, make DNS lookups to other DNSCrypt resolvers, and you can use it as a library in your own projects. * [Command-line tool](#commandline) + * [How to install](#install) * [Running a server](#runningserver) * [Making lookups](#lookup) * [Programming interface](#api) @@ -17,6 +23,11 @@ This repo includes everything you need to work with DNSCrypt. You can run your o Please note, that even though this tool can work as a server, it's purpose is merely testing. Use [dnsproxy](https://github.com/AdguardTeam/dnsproxy) or [AdGuard Home](https://github.com/AdguardTeam/AdGuardHome) for real-life purposes. + +### How to install + +Download and unpack an archive for your platform from the [latest release](https://github.com/ameshkov/dnscrypt/releases). + ### Running a server Generate a configuration file for running a DNSCrypt server: From c61ddcb786b19cde59725925776f6a320eb9c027 Mon Sep 17 00:00:00 2001 From: Andrey Meshkov Date: Mon, 19 Oct 2020 17:31:54 +0300 Subject: [PATCH 3/3] Set target coverage to 60% --- .codecov.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.codecov.yml b/.codecov.yml index 1a01bc2..0b9ff9c 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -2,7 +2,7 @@ coverage: status: project: default: - target: 65% + target: 60% threshold: null patch: false changes: false