├── .gitignore
├── CMakeLists.txt
├── Dockerfile
├── LICENSE
├── README.md
├── assets
├── README.md
├── china-ip.py
├── chinalist.py
└── gfwlist.py
├── include
├── applet
│ ├── adguard.h
│ ├── crontab.h
│ ├── dnsproxy.h
│ └── overture.h
├── bcrypt
│ ├── bcrypt.h
│ └── blowfish
│ │ ├── crypt.h
│ │ ├── crypt_blowfish.h
│ │ ├── crypt_gensalt.h
│ │ └── ow-crypt.h
├── common
│ ├── json.h
│ ├── structure.h
│ ├── sundry.h
│ └── system.h
├── constant.h.in
├── loader
│ ├── config.h
│ ├── default.h
│ ├── loader.h
│ └── parser.h
├── to_json.h
└── utils
│ ├── assets.h
│ ├── cJSON.h
│ ├── logger.h
│ └── process.h
└── src
├── CMakeLists.txt
├── Cargo.lock
├── Cargo.toml
├── applet
├── CMakeLists.txt
├── adguard.c
├── crontab.c
├── dnsproxy.c
└── overture.c
├── assets
├── Cargo.toml
├── cbindgen.toml
└── src
│ ├── fetch.rs
│ ├── ffi.rs
│ └── lib.rs
├── bcrypt
├── CMakeLists.txt
├── bcrypt.c
├── blowfish
│ ├── CMakeLists.txt
│ ├── crypt_blowfish.c
│ ├── crypt_gensalt.c
│ └── wrapper.c
└── hash.c
├── cleardns.c
├── common
├── CMakeLists.txt
├── json.c
├── structure.c
├── sundry.c
└── system.c
├── loader
├── CMakeLists.txt
├── config.c
├── default.c
├── loader.c
└── parser.c
├── to-json
├── Cargo.toml
├── cbindgen.toml
└── src
│ ├── ffi.rs
│ ├── lib.rs
│ └── parser.rs
└── utils
├── CMakeLists.txt
├── assets.c
├── cJSON.c
├── logger.c
└── process.c
/.gitignore:
--------------------------------------------------------------------------------
1 | /bin/
2 | /.idea/
3 | /src/target/
4 | /cmake-build/
5 | /cmake-build-debug/
6 | /cmake-build-release/
7 |
8 | /assets/*.txt
9 | /include/constant.h
10 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 2.8.12)
2 |
3 | project(cleardns LANGUAGES C)
4 |
5 | set(CMAKE_C_STANDARD 99)
6 |
7 | set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin/)
8 |
9 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Werror")
10 |
11 | ###############################################################
12 |
13 | if(NOT CMAKE_BUILD_TYPE)
14 | set(CMAKE_BUILD_TYPE Release)
15 | endif()
16 |
17 | ###############################################################
18 |
19 | macro(git_tag _tag)
20 | find_package(Git QUIET)
21 | if (GIT_FOUND)
22 | execute_process(
23 | COMMAND ${GIT_EXECUTABLE} describe --tags
24 | OUTPUT_VARIABLE ${_tag}
25 | OUTPUT_STRIP_TRAILING_WHITESPACE
26 | WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
27 | )
28 | endif()
29 | endmacro()
30 |
31 | set(VERSION "")
32 | git_tag(VERSION)
33 | if(VERSION STREQUAL "") # without git tag
34 | message(FATAL_ERROR "Unable to get version information")
35 | endif()
36 |
37 | ###############################################################
38 |
39 | add_subdirectory(src)
40 |
41 | ###############################################################
42 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | ARG ALPINE="alpine:3.21"
2 | ARG NODE="node:20-alpine3.21"
3 | ARG RUST="rust:1.86-alpine3.21"
4 | ARG GOLANG="golang:1.24-alpine3.21"
5 |
6 | FROM ${GOLANG} AS dnsproxy
7 | ENV DNSPROXY="0.75.3"
8 | RUN wget https://github.com/AdguardTeam/dnsproxy/archive/v${DNSPROXY}.tar.gz -O- | tar xz
9 | WORKDIR ./dnsproxy-${DNSPROXY}/
10 | RUN go get
11 | RUN env CGO_ENABLED=0 go build -v -trimpath -ldflags "-X main.VersionString=${DNSPROXY} -s -w"
12 | RUN mv dnsproxy /tmp/
13 |
14 | FROM ${GOLANG} AS overture
15 | ENV OVERTURE="1.8"
16 | RUN wget https://github.com/shawn1m/overture/archive/v${OVERTURE}.tar.gz -O- | tar xz
17 | WORKDIR ./overture-${OVERTURE}/main/
18 | RUN go get
19 | RUN env CGO_ENABLED=0 go build -v -trimpath -ldflags "-X main.version=v${OVERTURE} -s -w"
20 | RUN mv main /tmp/overture
21 |
22 | FROM ${ALPINE} AS adguard-src
23 | RUN apk add git
24 | ENV ADGUARD="0.107.60"
25 | RUN git clone https://github.com/AdguardTeam/AdGuardHome.git -b v${ADGUARD} --depth=1
26 |
27 | FROM ${NODE} AS adguard-web
28 | RUN apk add make
29 | COPY --from=adguard-src /AdGuardHome/ /AdGuardHome/
30 | WORKDIR /AdGuardHome/
31 | RUN echo '.nav-item .order-4 {display: none;}' >> ./client/src/components/Header/Header.css
32 | RUN make js-deps
33 | RUN make js-build
34 | RUN mv ./build/static/ /tmp/
35 |
36 | FROM ${GOLANG} AS adguard
37 | RUN apk add git make
38 | COPY --from=adguard-src /AdGuardHome/ /AdGuardHome/
39 | WORKDIR /AdGuardHome/
40 | RUN go get
41 | COPY --from=adguard-web /tmp/static/ ./build/static/
42 | RUN make CHANNEL="release" VERBOSE=1 go-build
43 | RUN mv AdGuardHome /tmp/
44 |
45 | FROM ${RUST} AS rust-mods
46 | RUN apk add musl-dev
47 | COPY ./src/ /cleardns/
48 | WORKDIR /cleardns/
49 | RUN cargo fetch
50 | RUN cargo build --release
51 | RUN mv ./target/release/*.a /tmp/
52 |
53 | FROM ${ALPINE} AS cleardns
54 | RUN apk add gcc git make cmake musl-dev
55 | COPY ./ /cleardns/
56 | COPY --from=rust-mods /tmp/libassets.a /cleardns/src/target/release/
57 | COPY --from=rust-mods /tmp/libto_json.a /cleardns/src/target/release/
58 | WORKDIR /cleardns/bin/
59 | RUN cmake -DCMAKE_EXE_LINKER_FLAGS=-static .. && make && strip cleardns
60 | RUN mv cleardns /tmp/
61 |
62 | FROM ${ALPINE} AS assets
63 | RUN apk add xz
64 | RUN wget https://cdn.dnomd343.top/cleardns/gfwlist.txt.xz
65 | RUN wget https://cdn.dnomd343.top/cleardns/china-ip.txt.xz
66 | RUN wget https://cdn.dnomd343.top/cleardns/chinalist.txt.xz
67 | RUN xz -d *.xz && tar cJf /tmp/assets.tar.xz gfwlist.txt china-ip.txt chinalist.txt
68 |
69 | FROM ${ALPINE} AS release
70 | RUN apk add upx xz
71 | COPY --from=assets /tmp/assets.tar.xz /release/
72 | COPY --from=cleardns /tmp/cleardns /release/usr/bin/
73 | COPY --from=dnsproxy /tmp/dnsproxy /release/usr/bin/
74 | COPY --from=overture /tmp/overture /release/usr/bin/
75 | COPY --from=adguard /tmp/AdGuardHome /release/usr/bin/
76 | WORKDIR /release/usr/bin/
77 | RUN ls | xargs -n1 -P0 upx -9
78 |
79 | FROM ${ALPINE}
80 | COPY --from=release /release/ /
81 | WORKDIR /cleardns/
82 | ENTRYPOINT ["cleardns"]
83 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Dnomd343
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ClearDNS
2 |
3 | + ✅ 无污染的 DNS 解析,避开运营商和防火长城的污染与劫持
4 |
5 | + ✅ 支持多种加密协议,包括 DoH 、DoT 、DoQ 与 DNSCrypt
6 |
7 | + ✅ 内置详细的分流规则,同时也支持自定义域名和 CIDR 策略
8 |
9 | + ✅ DNS 请求审计,记录不同设备的查询、拦截、返回等信息
10 |
11 | + ✅ 自定义拦截规则,可屏蔽指定应用,如 QQ 、微信、微博等
12 |
13 | + ✅ 强制 hosts 功能,将指定域名直接解析到指定 IP 上
14 |
15 | + ✅ IPv6 支持,拦截特定的 DNS 请求类型,修改指定域名的 TTL
16 |
17 | + ✅ 在 DNS 层面上实现去广告与防跟踪功能,按需求配置自定义规则
18 |
19 | ---
20 |
21 | 由于 ClearDNS 工作在服务器一侧,因此对 APP 、网页、机顶盒、IoT 设备等均可生效;它一般部署在内网中,为局域网设备提供服务,建议运行在内网一台长期开机的设备上(主路由、树莓派、小主机、旁路由、NAS 设备等),同时 ClearDNS 也可部署在公网服务器上,面向国内网络提供无污染服务。
22 |
23 | ## 设计架构
24 |
25 | > AdGuardHome 用于加载拦截规则,可以自定义是否开启
26 |
27 | ```mermaid
28 | graph LR
29 | input{{Input}} -.-> adguard(AdGuardHome)
30 | subgraph ClearDNS
31 | adguard --> diverter(Diverter)
32 | diverter --> domestic(Domestic)
33 | diverter --> foreign(Foreign)
34 | end
35 | domestic -. Plain DNS .-> domestic_1(223.5.5.5)
36 | domestic -. DNS over TLS .-> domestic_2(tls://dot.pub)
37 | foreign -. DNS over QUIC .-> foreign_1(Private Server)
38 | foreign -. DNS over HTTPS .-> foreign_2(Private Server)
39 | ```
40 |
41 | DNS 请求在通过 AdGuardHome 处理后,发往分流器 Diverter ,在这里将借助路由资源、国内组 Domestic 与国外组 Foreign 的返回结果,甄别出被污染的数据,返回正确的 DNS 解析;两组请求都可拥有多个上游服务器,ClearDNS 可以逐个对服务器进行请求,亦可同时发起查询。
42 |
43 | ClearDNS 支持多种 DNS 协议,首先是常规 DNS ,即基于 UDP 或 TCP 的明文查询,该方式无法抵抗 DNS 污染,对部分运营商有效(相当于不使用运营商分配的 DNS 服务器),仅建议用于国内无劫持的环境下使用;其次为 `DNS over HTTPS` 、`DNS over TLS` 、`DNS over QUIC` 与 `DNSCrypt` ,它们都是加密的 DNS 服务协议,可以抵抗污染与劫持行为,但可能被防火长城拦截;在出境请求中,`DNS over TLS` 特别是标准端口的服务已经被大规模封杀,`DNSCrypt` 也基本无法使用,目前建议使用 `DNS over QUIC` 与非标准路径的 `DNS over HTTPS` 服务。
44 |
45 | 对于多种 DNS 加密协议的简述,可以参考[浅谈DNS协议](https://blog.dnomd343.top/dns-server/#DNS%E5%90%84%E5%8D%8F%E8%AE%AE%E7%AE%80%E4%BB%8B),里面讲解了不同协议的区别与优缺点,以及 DNS 服务的分享格式。
46 |
47 | 在分流器部分,ClearDNS 需要借助三个资源文件工作:
48 |
49 | + `gfwlist.txt` :记录常见的被墙域名
50 |
51 | + `chinalist.txt` :记录服务器在国内的常见域名
52 |
53 | + `china-ip.txt` :记录国内 IP 段数据(CIDR 格式)
54 |
55 | > 防火长城的 DNS 污染有一个特点,被污染的结果必为境外 IP 地址
56 |
57 | 当分流器接到请求时,若在 `chinalist.txt` 中有所匹配,则只请求国内组,若在 `gfwlist.txt` 中匹配,则仅请求国外组;两者均未未匹配的情况下,将同时请求国内组与国外组,若国内组返回结果在 `china-ip.txt` 中,则证明 DNS 未被污染,采纳国内组结果,若返回国外 IP 地址,则可能已经被污染,将选取国外组结果。
58 |
59 | 由于以上资源数据一直在变动,ClearDNS 内置了更新功能,用于自动获取新的资源文件;本项目提供了默认分流配置文件,从多个上游项目收集后合并,每天零点更新一次,数据处理的源码可见[此处](./assets),下发地址如下:
60 |
61 | + `gfwlist.txt` :`https://res.343.re/Share/cleardns/gfwlist.txt`
62 |
63 | + `china-ip.txt` :`https://res.343.re/Share/cleardns/china-ip.txt`
64 |
65 | + `chinalist.txt` :`https://res.343.re/Share/cleardns/chinalist.txt`
66 |
67 | 国内用户直接访问下载站可能偏慢,建议使用以下镜像地址:
68 |
69 | + `gfwlist.txt` :`https://cdn.dnomd343.top/cleardns/gfwlist.txt`
70 |
71 | + `china-ip.txt` :`https://cdn.dnomd343.top/cleardns/china-ip.txt`
72 |
73 | + `chinalist.txt` :`https://cdn.dnomd343.top/cleardns/chinalist.txt`
74 |
75 | 在 ClearDNS 的默认配置文件中,使用了本项目的分流资源作为更新上游,您可以修改配置,指向自定义资源(支持多个本地或远程文件),也可禁用更新。
76 |
77 | ## 命令行参数
78 |
79 | 在 ClearDNS 启动时,可以指定以下命令行参数:
80 |
81 | + `--config` :指定配置文件名称,默认值为 `cleardns.yml`
82 |
83 | + `--verbose` :打印完整的日志信息
84 |
85 | + `--debug` :进入 Debug 模式
86 |
87 | + `--version` :显示版本信息
88 |
89 | + `--help` :显示帮助信息
90 |
91 | ClearDNS 也支持通过环境变量指定参数,其优先级低于命令行参数:
92 |
93 | + `CONFIG=my_config.yml` :指定配置文件名称,默认值为 `cleardns.yml`
94 |
95 | + `VERBOSE=TRUE` :打印完整的日志信息
96 |
97 | + `DEBUG=TRUE` :进入 Debug 模式
98 |
99 | ## 配置格式
100 |
101 | ClearDNS 支持 JSON 、YAML 与 TOML 格式的配置文件,默认配置如下:
102 |
103 | ```yaml
104 | port: 53
105 |
106 | cache:
107 | enable: true
108 | size: 4194304
109 | optimistic: true
110 |
111 | diverter:
112 | port: 5353
113 |
114 | adguard:
115 | enable: true
116 | port: 80
117 | username: admin
118 | password: cleardns
119 |
120 | domestic:
121 | port: 4053
122 | bootstrap: 223.5.5.5
123 | primary:
124 | - tls://dns.alidns.com
125 | - https://doh.pub/dns-query
126 | fallback:
127 | - 223.6.6.6
128 | - 119.29.29.29
129 |
130 | foreign:
131 | port: 6053
132 | bootstrap: 8.8.8.8
133 | primary:
134 | - tls://dns.google
135 | - https://dns.cloudflare.com/dns-query
136 | fallback:
137 | - 1.1.1.1
138 | - 8.8.4.4
139 |
140 | assets:
141 | cron: "0 4 * * *"
142 | update:
143 | gfwlist.txt: https://cdn.dnomd343.top/cleardns/gfwlist.txt
144 | china-ip.txt: https://cdn.dnomd343.top/cleardns/china-ip.txt
145 | chinalist.txt: https://cdn.dnomd343.top/cleardns/chinalist.txt
146 | ```
147 |
148 | ### Port
149 |
150 | DNS 服务端口,支持常规的 TCP 与 UDP 查询,默认为 `53` ;若您想开放 `DNS over TLS` 、`DNS over HTTPS` 等其他协议的服务,可以在 AdGuardHome 中进行具体配置。
151 |
152 | ### Cache
153 |
154 | DNS 缓存配置,此处与 AdGuardHome 中的缓存不相关,建议打开其中一个即可。
155 |
156 | ```yaml
157 | cache:
158 | size: 0
159 | enable: false
160 | optimistic: false
161 | ```
162 |
163 | + `enable` :是否开启 DNS 缓存,默认为 `false`
164 |
165 | + `size` :DNS 缓存容量,单位为字节,开启时建议设置在 `64k` 到 `4m` 量级,默认为 `0`
166 |
167 | + `optimistic` :DNS 乐观缓存,开启后当记录过期时,仍然返回上一次查询结果,但 TTL 修改为 10 ,同时立即向上游发起查询;由于绝大多数 DNS 记录在 TTL 期限内不会发生变化,这个机制可以显著减少请求平均延迟,但一旦出现变动,访问目标必须等待 10 秒后解析刷新才恢复正常。
168 |
169 | ### AdGuard
170 |
171 | AdGuardHome 配置选项,此处选项将在每次重启后覆盖 AdGuardHome 的网页端配置。
172 |
173 | ```yaml
174 | adguard:
175 | enable: true
176 | port: 80
177 | username: admin
178 | password: cleardns
179 | ```
180 |
181 | + `enable` :是否开启 AdGuardHome 功能,默认为 `false`
182 |
183 | + `port` :AdGuardHome 网页服务端口,默认为 `80`
184 |
185 | + `username` :AdGuardHome 登录用户名,默认为 `admin`
186 |
187 | + `password` :AdGuardHome 登录密码,默认为 `cleardns`
188 |
189 | ### Diverter
190 |
191 | DNS 分流器选项,指定端口与分流规则
192 |
193 | ```yaml
194 | diverter:
195 | port: 5353
196 | gfwlist: []
197 | china_ip: []
198 | chinalist: []
199 | ```
200 |
201 | + `port` :DNS 分流器端口,若 AdGuardHome 关闭,本选项将失效,默认为 `5353`
202 |
203 | > 以下选项用于添加自定义规则,将合并在资源文件上
204 |
205 | + `gfwlist` :自定义的 GFW 拦截域名列表,针对该域名的查询将屏蔽 `domestic` 组结果,默认为空
206 |
207 | + `chinalist` :自定义的国内域名列表,针对该域名的查询将屏蔽 `foreign` 组结果,默认为空
208 |
209 | + `china-ip` :自定义的国内 IP 段,`domestic` 组返回内容若命中则采纳,否则使用 `foreign` 组结果,默认为空
210 |
211 | ### Domestic
212 |
213 | 国内组 DNS 配置选项
214 |
215 | ```yaml
216 | domestic:
217 | port: 4053
218 | ipv6: true
219 | verify: true
220 | parallel: true
221 | bootstrap: ...
222 | primary:
223 | - ...
224 | - ...
225 | fallback:
226 | - ...
227 | - ...
228 | ```
229 |
230 | + `port` :国内组 DNS 端口,默认为 `4053`
231 |
232 | + `ipv6` :是否允许 IPv6 查询,关闭后将屏蔽 `AAAA` 请求,默认为 `true`
233 |
234 | + `verify` :是否验证证书合法性,关闭后允许无效的 TLS 证书,默认为 `true`
235 |
236 | + `parallel` :是否对多个上游进行并行查询,默认为 `true`
237 |
238 | + `bootstrap` :引导 DNS 服务器,用于 `primary` 与 `fallback` 中服务器域名的查询,仅允许常规 DNS 服务,默认为空
239 |
240 | + `primary` :主 DNS 服务器列表,用于默认情况下的查询
241 |
242 | + `fallback` :备用 DNS 服务器列表,当 `primary` 中 DNS 服务器宕机时,回落到本处再次查询
243 |
244 | ### Foreign
245 |
246 | 国外组 DNS 配置选项
247 |
248 | ```yaml
249 | foreign:
250 | port: 6053
251 | ipv6: true
252 | verify: true
253 | parallel: true
254 | bootstrap: ...
255 | primary:
256 | - ...
257 | - ...
258 | fallback:
259 | - ...
260 | - ...
261 | ```
262 |
263 | + `port` :国外组 DNS 端口,默认为 `6053`
264 |
265 | > Foreign 选项意义与 Domestic 中相同,可参考上文说明
266 |
267 | ### Reject
268 |
269 | DNS 拒绝类型列表,指定屏蔽的 DNS 记录类型,不同 DNS 类型编号可参考[DNS记录类型](https://en.wikipedia.org/wiki/List_of_DNS_record_types),默认为空。
270 |
271 | ```yaml
272 | reject:
273 | - 255 # ANY
274 | ```
275 |
276 | ### Hosts
277 |
278 | Hosts 记录列表,指定域名对应 IP 地址,支持正则匹配,默认为空。
279 |
280 | ```yaml
281 | hosts:
282 | - "10.0.0.1 example.com$"
283 | - "..."
284 | ```
285 |
286 | ### TTL
287 |
288 | 域名过期时间列表,修改指定域名返回的 TTL 数值,支持正则表达式匹配,默认为空。
289 |
290 | ```yaml
291 | ttl:
292 | - "example.com$ 300"
293 | - "..."
294 | ```
295 |
296 | ### Custom
297 |
298 | 自定义脚本,将在 ClearDNS 初始化后,即将启动前执行。
299 |
300 | > 本功能用于注入自定义功能,基于 Alpine 的 `ash` 执行,可能不支持部分 `bash` 语法。
301 |
302 | ```yaml
303 | custom:
304 | - "echo 'Hello World!'"
305 | - "..."
306 | ```
307 |
308 | ### Assets
309 |
310 | 资源文件升级选项,用于自动更新分流资源。
311 |
312 | ```yaml
313 | assets:
314 | disable: false
315 | cron: "0 4 * * *"
316 | update:
317 | gfwlist.txt: https://cdn.dnomd343.top/cleardns/gfwlist.txt
318 | china-ip.txt: https://cdn.dnomd343.top/cleardns/china-ip.txt
319 | chinalist.txt:
320 | - https://cdn.dnomd343.top/cleardns/chinalist.txt
321 | - /tmp/chinalist-local.txt
322 | - demo.list # aka `${WorkDir}/assets/demo.list`
323 | custom.txt:
324 | - https://.../my-custom-asset.txt
325 | ```
326 |
327 | + `disable` :是否关闭资源文件加载,默认为 `false`
328 |
329 | + `cron` :指定触发升级的 Crontab 表达式
330 |
331 | > 某一资源文件指定多个升级目标时,若其中任意一个出错,将导致该资源文件升级失败。
332 |
333 | > 对于远程 URL 资源,ClearDNS 将尝试进行拉取,若发生网络错误将会重试,最多进行三次。
334 |
335 | + `update` :指定资源升级的上游,支持远程 URL 和本地文件,支持指定多个目标,将自动去重后合并。
336 |
337 | ## 部署教程
338 |
339 | ### 1. 网络配置
340 |
341 | > 本项目基于 Docker 构建,在 [Docker Hub](https://hub.docker.com/r/dnomd343/cleardns) 或 [Github Package](https://github.com/dnomd343/ClearDNS/pkgs/container/cleardns) 可以查看已构建的各版本镜像。
342 |
343 | ClearDNS 基于 Docker 网络有以下三种部署模式:
344 |
345 | > 如果您对以网络知识不熟悉,或只想开箱即用,使用 Bridge 模式即可。
346 |
347 | | | Host 模式 | Bridge 模式 | Macvlan 模式 |
348 | |:----------:|:----------------------------------------:|:------------------------:|:--------------------:|
349 | | 网络原理 | 宿主机网络 | 桥接网络 | 虚拟独立 mac 网卡 |
350 | | 服务 IP | 宿主机 IP | 宿主机 IP | 容器独立 IP |
351 | | 宿主机 IP | 静态 IP 地址 | 静态 IP 地址 | 静态/动态 IP 地址 |
352 | | 宿主机网络 | 无需改动网络配置 | Docker 自动适配 | 手动修改底层网络配置 |
353 | | 宿主机端口 | 占用宿主机 53, 80, 4053, 5353, 6053 端口 | 占用宿主机 53 与 80 端口 | 不占用端口 |
354 | | 管理完整性 | 完全 | 无法区分客户端 | 完全 |
355 | | 宿主机耦合 | 强耦合 | 一般耦合 | 链路层以上完全分离 |
356 | | 网络性能 | 相对较高 | 相对较低 | 相对适中 |
357 | | 部署难度 | 简单 | 简单 | 复杂 |
358 |
359 | > 不熟悉 Linux 网络配置请勿使用 Macvlan 模式,新手建议选择 Bridge 或 Host 模式。
360 |
361 | 以下操作均于 root 用户下执行
362 |
363 | ```bash
364 | # 检查Docker环境
365 | $ docker --version
366 | ··· Docker 版本信息 ···
367 |
368 | # 无Docker环境请先执行安装
369 | $ wget -qO- https://get.docker.com/ | bash
370 | ··· Docker 安装日志 ···
371 | ```
372 |
373 | > 下述命令中,容器路径可替换为上述其他源,国内网络可优先选择阿里云仓库
374 |
375 | ClearDNS 同时发布在多个镜像源上:
376 |
377 | + `Docker Hub` :`dnomd343/cleardns`
378 |
379 | + `Github Package` :`ghcr.io/dnomd343/cleardns`
380 |
381 | + `阿里云镜像` :`registry.cn-shenzhen.aliyuncs.com/dnomd343/cleardns`
382 |
383 | > 由于容器默认为 UTC-0 时区,将导致日志时间偏差 8 小时,映射 `/etc/timezone` 与 `/etc/localtime` 文件用于同步时区信息
384 |
385 |
386 |
387 | Bridge 模式
388 |
389 |
390 |
391 | 检查相关端口状态:
392 |
393 | ```bash
394 | netstat -tlnpu | grep -E ":53|:80"
395 | ```
396 |
397 | + 若 `TCP/53` 或 `UDP/53` 被占用,请先关闭对应进程
398 |
399 | + 若 `TCP/80` 端口被占用,可以关闭对应进程,也可换用其他端口
400 |
401 | 启动 ClearDNS 容器:
402 |
403 | ```bash
404 | docker run -dt \
405 | --restart always \
406 | --name cleardns --hostname cleardns \
407 | --volume /etc/cleardns/:/cleardns/ \
408 | --volume /etc/timezone:/etc/timezone:ro \
409 | --volume /etc/localtime:/etc/localtime:ro \
410 | -p 53:53/udp -p 53:53 -p 80:80 \
411 | dnomd343/cleardns
412 | ```
413 |
414 | 或使用以下等效 `docker-compose` 配置:
415 |
416 | ```yml
417 | version: '3'
418 | services:
419 | cleardns:
420 | hostname: cleardns
421 | container_name: cleardns
422 | image: dnomd343/cleardns
423 | network_mode: bridge
424 | restart: always
425 | tty: true
426 | ports:
427 | - '53:53'
428 | - '80:80'
429 | - '53:53/udp'
430 | volumes:
431 | - ./:/cleardns/
432 | - /etc/timezone:/etc/timezone:ro
433 | - /etc/localtime:/etc/localtime:ro
434 | ```
435 |
436 |
437 |
438 |
439 |
440 | Host 模式
441 |
442 |
443 |
444 | 检查相关端口状态:
445 |
446 | ```bash
447 | netstat -tlnpu | grep -E ":53|:80|:4053|:5353|:6053"
448 | ```
449 |
450 | + 若 `TCP/53` 或 `UDP/53` 被占用,请先关闭对应进程
451 |
452 | + 若 `TCP/80` 端口被占用,可以关闭对应进程,也可换用其他端口
453 |
454 | + 若 `TCP/4053` 、`UDP/4053` 、`TCP/6053` 、`UDP/6053` 、`TCP/5353` 、`UDP/5353` 被占用,请先关闭对应进程或换用其他空闲端口
455 |
456 | 启动 ClearDNS 容器:
457 |
458 | ```bash
459 | docker run -dt --network host \
460 | --restart always \
461 | --name cleardns --hostname cleardns \
462 | --volume /etc/cleardns/:/cleardns/ \
463 | --volume /etc/timezone:/etc/timezone:ro \
464 | --volume /etc/localtime:/etc/localtime:ro \
465 | dnomd343/cleardns
466 | ```
467 |
468 | 或使用以下等效 `docker-compose` 配置:
469 |
470 | ```yml
471 | version: '3'
472 | services:
473 | cleardns:
474 | hostname: cleardns
475 | container_name: cleardns
476 | image: dnomd343/cleardns
477 | network_mode: host
478 | restart: always
479 | tty: true
480 | volumes:
481 | - ./:/cleardns/
482 | - /etc/timezone:/etc/timezone:ro
483 | - /etc/localtime:/etc/localtime:ro
484 | ```
485 |
486 |
487 |
488 |
489 |
490 | Macvlan 模式
491 |
492 |
493 |
494 | 启动容器前需要创建 `macvlan` 网络,以下选项需按实际网络配置:
495 |
496 | ```bash
497 | # 开启eth0网卡混杂模式
498 | $ ip link set eth0 promisc on
499 |
500 | # 创建macvlan网络,按实际情况指定网络信息
501 | $ docker network create -d macvlan \
502 | --subnet=IPv4网段 --gateway=IPv4网关 \
503 | --subnet=IPv6网段 --gateway=IPv6网关 \ # IPv6可选
504 | --ipv6 -o parent=eth0 macvlan # 在eth0网卡上运行
505 | ```
506 |
507 | 启动 ClearDNS 容器:
508 |
509 | ```bash
510 | # privileged 权限可选
511 | docker run -dt --network macvlan \
512 | --restart always --privileged \
513 | --name cleardns --host cleardns \
514 | --volume /etc/cleardns/:/cleardns/ \
515 | --volume /etc/timezone:/etc/timezone:ro \
516 | --volume /etc/localtime:/etc/localtime:ro \
517 | --ip IPv4地址 --ip6 IPv6地址 \
518 | dnomd343/cleardns
519 | ```
520 |
521 | 或使用以下等效 `docker-compose` 配置:
522 |
523 | ```yml
524 | version: '3'
525 | services:
526 | cleardns:
527 | hostname: cleardns
528 | container_name: cleardns
529 | image: dnomd343/cleardns
530 | privileged: true
531 | restart: always
532 | tty: true
533 | volumes:
534 | - ./:/cleardns/
535 | - /etc/timezone:/etc/timezone:ro
536 | - /etc/localtime:/etc/localtime:ro
537 | networks:
538 | macvlan:
539 | ipv4_address: IPv4地址
540 | ipv6_address: IPv6地址
541 |
542 | networks:
543 | macvlan:
544 | external: true
545 | ```
546 |
547 | > 以下配置旨在让宿主机能够使用 ClearDNS ,若无此需求可以跳过
548 |
549 | 宿主机网络更改配置,以下示例基于 Debian 系发行版:
550 |
551 | ```bash
552 | vim /etc/network/interfaces
553 | ```
554 |
555 | 添加以下内容,创建网桥连接宿主机,按实际情况指定网络信息:
556 |
557 | > 以下配置文件仅用于示例,未执行 IPv6 配置
558 |
559 | ```ini
560 | auto eth0
561 | iface eth0 inet manual
562 |
563 | auto macvlan
564 | iface macvlan inet static
565 | address 192.168.2.34 # 宿主机静态IP地址
566 | netmask 255.255.255.0 # 子网掩码
567 | gateway 192.168.2.1 # 虚拟网关IP地址
568 | dns-nameservers 192.168.2.3 # DNS服务器
569 | pre-up ip link add macvlan link eth0 type macvlan mode bridge # 宿主机网卡上创建网桥
570 | post-down ip link del macvlan link eth0 type macvlan mode bridge # 退出时删除网桥
571 | ```
572 |
573 | 重启宿主机网络生效(或直接重启系统):
574 |
575 | ```bash
576 | $ /etc/init.d/networking restart
577 | [ ok ] Restarting networking (via systemctl): networking.service.
578 | ```
579 |
580 |
581 |
582 | ---
583 |
584 | ClearDNS 会将数据持久化存储,以在重启 Docker 或宿主机后保留配置及日志,上述命令将文件存储在工作目录 `/etc/cleardns` 下,您可以根据需要更改此目录。
585 |
586 | 如果需要恢复 ClearDNS 初始化配置,删除该目录并重启容器即可。
587 |
588 | ### 2. 修改配置文件
589 |
590 | > 国外组服务器切勿使用常规 DNS 服务,例如 `8.8.8.8` ,由于请求信息为明文,GFW 会抢答回复数据,导致内容仍然受到污染。
591 |
592 | 在 `cleardns.yml` 中指定上游 DNS 服务器,国内组可指定国内公共 DNS 服务,国外组需指定可用的加密 DNS 服务,具体说明参考[关于上游的配置](#关于上游的配置)部分。
593 |
594 | > DNSCrypt 使用 `DNS Stamp` 封装,可以在[这里](https://dnscrypt.info/stamps)在线解析或生成链接内容。
595 |
596 | 各 DNS 协议格式示例如下:
597 |
598 | #### Plain DNS
599 |
600 | + `1.1.1.1`
601 |
602 | + `8.8.8.8`
603 |
604 | #### DNS-over-TLS
605 |
606 | + `tls://223.5.5.5`
607 |
608 | + `tls://dns.alidns.com`
609 |
610 | #### DNS-over-HTTPS
611 |
612 | + `https://dns.pub/dns-query`
613 |
614 | + `https://223.5.5.5/dns-query`
615 |
616 | #### DNS-over-QUIC
617 |
618 | + `quic://94.140.14.14`
619 |
620 | + `quic://dns.adguard.com`
621 |
622 | #### DNSCrypt
623 |
624 | + `sdns://AQIAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20`
625 |
626 | 修改配置文件后,重启 Docker 容器生效
627 |
628 | ```bash
629 | docker restart cleardns
630 | ```
631 |
632 | ### 3. 配置 AdGuardHome
633 |
634 | 浏览器打开 ClearDNS 服务,使用 Host 或 Bridge 模式时输入宿主机 IP 地址,使用 Macvlan 模式时输入容器 IP ,即可进入 AdGuardHome 配置界面,默认登录账号为 `admin` ,密码为 `cleardns` ,该选项可在配置文件中修改,登录后进入 AdGuardHome 管理界面。
635 |
636 | 在 `设置` - `DNS设置` 中可以更改相关 DNS 选项,建议启用 DNSSEC ,若配置文件中禁用了出口缓存,可在此处配置缓存选项,内存允许的情况下适当拉大缓存大小,并开启乐观缓存,具体说明参考[关于缓存配置](#关于缓存的配置)部分。
637 |
638 | 在 DNS 封锁清单中,可配置以下规则:
639 |
640 | + `AdGuard` :`https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt`
641 |
642 | + `Anti-AD` :`https://anti-ad.net/easylist.txt`
643 |
644 | + `AdAway` :`https://adaway.org/hosts.txt`
645 |
646 | + `乘风规则` :`https://res.343.re/Share/Adblock-Rules/xinggsf.txt`
647 |
648 | > 配置过多的规则会导致设备负载变大,请酌情添加。
649 |
650 | ### 5. 配置 DHCP 信息
651 |
652 | 若您的服务部署在内网,为了使 ClearDNS 生效,需要在路由器 DHCP 服务中指定 DNS 服务器,使用 Host 与 Bridge 模式时,指定为宿主机 IP ,使用 Macvlan 模式指定为容器 IP ,设备重新接入网络后即可生效。
653 |
654 | > 对于内网中一些固定 IP 的设备,需要手动更改其 DNS 为上述 IP 地址。
655 |
656 | ## 补充说明
657 |
658 | ### 关于上游的配置
659 |
660 | ClearDNS 上游分为国内组 `Domestic` 与国外组 `Foreign` ,它们的配置逻辑不尽相同,下面分别说明:
661 |
662 | 国内组的目标在于避免劫持的前提下,尽量选择延迟低的上游服务器;我们可以选择运营商提供的 DNS 服务器,或者大公司搭建的公共 DNS 服务器(例如 AliDNS 、DNSPod 、114DNS 等),后者大部分都支持加密查询。
663 |
664 | 此前,一些公司会在不同运营商机房部署服务器,加快用户访问速度,在早期运营不完善时使用公共 DNS 服务器,时常会出现电信用户得到联通机房的解析,导致访问卡顿,而运营商分配的 DNS 一般就不存在这个问题,因此一些老教程会建议用户使用运营商解析。但是最近几年,基于访问来源来确定的解析已经成熟,大多数情况下不会出错,况且大公司的服务一般有多线 BGP 接入,已经基本摒弃基于 DNS 的线路分流机制。所以,使用运营商 DNS 服务仅在延迟上有一点优势(正常情况下延迟会低几毫秒),在确定不存在 DNS 污染行为后,您可以根据延迟实测结果确定是否使用。
665 |
666 | 如果运营商不存在 DNS 劫持行为,向公共 DNS 查询时,使用常规 DNS 与加密 DNS 并没有明显区别,理论上前者延迟会低一些,但几毫秒的差距在实际使用中基本可以忽略,您如果比较在意访问隐私性,建议使用加密链路,总体上利大于弊。
667 |
668 | 国外组的目标在于避开污染的前提下,选择更稳定与延迟更低的上游;鉴于目前绝大多数国外公共 DNS 加密服务已经被防火长城屏蔽,因此我们只能考虑自建服务器,或者使用共享的跨境服务器(例如 DH-DNS 、LoopDNS 、IQDNS 、Tex0n DNS 等公益项目)。
669 |
670 | 如果您有国外的服务器,可以考虑搭建一条线路自用,具体的教程可以参考[搭建全协议DNS服务器](https://blog.dnomd343.top/dns-server/),截至目前,不建议开启 `DNS-over-TLS` 功能与标准路径的 `DNS-over-HTTPS` 服务,它们基本会被防火长城秒封,且可能波及整个服务器 IP ,更保险的做法是搭建非标准端口的 `DNS-over-QUIC` 服务,或者将 `DNS-over-HTTPS` 隐藏在博客、网盘等正常网页下面,提高存活性。
671 |
672 | 自建 DNS 服务器的延迟与跨境线路质量直接相关,普通的自建服务延迟可能偏高,而共享的 DNS 跨境服务器一般通过专线接入国内中转,且使用人数多时热缓存命中率高,延迟相对可以低不少,您可以同时配置两者作为国外组上游,提高整套系统的稳定性。
673 |
674 | 最后,上游服务器不建议配置过多,否则网络压力偏大的同时不会带来明显的提升,建议两组的 `primary` 与 `fallback` 均配置 2 ~ 3 台上游即可。
675 |
676 | ### 关于缓存的配置
677 |
678 | 在 ClearDNS 默认配置中打开了缓存选项,该选项作用于国内组与国外组的出口,而非 AdGuardHome 缓存,两者建议打开一个即可;正常情况下,更建议仅在 AdGuardHome 中配置缓存,避免多余的缓存,同时减少重复查询在分流器上带来的额外开销。
679 |
680 | 缓存大小上,一般配置 4MiB 到 16MiB 即可,若运行 ClearDNS 的机子运行内存较小,可以选择性减少;实际个人使用中,不需要过大体积的缓存,绝大部分在超过 TTL 后仍不会重复命中,反而降低了系统索引性能。
681 |
682 | 乐观缓存功能用于降低请求延迟,该选项打开后,即使请求的记录已经过期,仍然返回过期数据,但会把 TTL 降低为 10 秒,避免客户端系统长时间缓存该数据,同时向上游发起请求,覆盖过期数据;若实际上该 DNS 记录发生更新,网站访问将会出错,只能等待 10 秒后系统 DNS 记录过期重新请求,拿到正确的 DNS 数据,若 DNS 记录未变动,则正常访问网站。
683 |
684 | 从乐观缓存的工作原理看,就是在赌 DNS 数据未出现更新,正常情况下 DNS 记录在 TTL 内发生变动的概率很低,且大部分 DNS 记录的超时时间在 300s 左右,这种方式可以让 DNS 请求平均延迟显著降低,但缺点就是一旦变动就会带来十几秒的延迟,此时网络体验影响较大。
685 |
686 | 乐观缓存按需打开即可,正常情况下,打开后平均延迟能降低到 10ms 以内,命中错误的频率很低,个人认为还是相对值得的。
687 |
688 | ### DNS 延迟测试
689 |
690 | 您可以借助 [dnslookup](https://github.com/ameshkov/dnslookup) 工具进行测试,使用以下命令多次测试后取平均值,可以大致反映延迟时长。
691 |
692 | ```bash
693 | $ time dnslookup baidu.com tls://dns.pub
694 | ··· DNS 查询返回 ···
695 |
696 | real 0m0.030s
697 | user 0m0.011s
698 | sys 0m0.005s
699 | ```
700 |
701 | ## 手动编译
702 |
703 | ### 本地构建
704 |
705 | ```bash
706 | $ git clone https://github.com/dnomd343/ClearDNS.git
707 | $ cd ./ClearDNS/
708 | $ docker build -t cleardns .
709 | ```
710 |
711 | ### 交叉构建
712 |
713 | ```bash
714 | $ git clone https://github.com/dnomd343/ClearDNS.git
715 | $ cd ./ClearDNS/
716 | $ docker buildx build \
717 | -t dnomd343/cleardns \
718 | -t ghcr.io/dnomd343/cleardns \
719 | -t registry.cn-shenzhen.aliyuncs.com/dnomd343/cleardns \
720 | --platform="linux/amd64,linux/arm64" . --push
721 | ```
722 |
723 | ## 许可证
724 |
725 | > NOTE: AdGuardHome will work as a child process, which will not violate the GPLv3 license. ClearDNS is based on the MIT license, which allows commercial use, but the AdGuardHome function should be turned off at this time.
726 |
727 | Thanks for [@AdguardTeam](https://github.com/AdguardTeam)'s [AdGuardHome](https://github.com/AdguardTeam/AdGuardHome.git) (GPL-3.0) project.
728 |
729 | Thanks for [@ameshkov](https://github.com/ameshkov)'s [dnsproxy](https://github.com/AdguardTeam/dnsproxy.git) (Apache-2.0) project.
730 |
731 | Thanks for [@shawn1m](https://github.com/shawn1m)'s [overture](https://github.com/shawn1m/overture.git) (MIT) project.
732 |
733 | MIT ©2022 [@dnomd343](https://github.com/dnomd343)
734 |
--------------------------------------------------------------------------------
/assets/README.md:
--------------------------------------------------------------------------------
1 | # 分流资源文件
2 |
3 | 本目录下的脚本用于拉取合并上游数据,生成以下对应的资源文件:
4 |
5 | + `gfwlist.txt` :常见的被墙域名
6 |
7 | + `chinalist.txt` :服务器在国内的常见域名
8 |
9 | + `china-ip.txt` :国内 IP 段数据(CIDR 格式)
10 |
11 | ## 运行说明
12 |
13 | 在运行前,请检查 `netaddr` 模块是否安装,若无则执行以下命令:
14 |
15 | ```bash
16 | pip3 install netaddr
17 | ```
18 |
19 | > 由于 Python 的性能问题,脚本对 CIDR 的合并效率偏低,获取国内 IP 段可能花费较长时间。
20 |
21 | ```bash
22 | $ ./gfwlist.py
23 | $ ./china-ip.py
24 | $ ./chinalist.py
25 | $ ls
26 | china-ip.txt chinalist.txt gfwlist.txt ...
27 | ```
28 |
29 | ## 上游信息
30 |
31 | ### gfwlist
32 |
33 | ```yaml
34 | https://github.com/gfwlist/gfwlist/raw/master/gfwlist.txt
35 |
36 | https://github.com/hq450/fancyss/raw/master/rules/gfwlist.conf
37 |
38 | https://github.com/Loukky/gfwlist-by-loukky/raw/master/gfwlist.txt
39 |
40 | https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/gfw.txt
41 | ```
42 |
43 | ### china-ip
44 |
45 | ```yaml
46 | https://gaoyifan.github.io/china-operator-ip/
47 |
48 | https://github.com/misakaio/chnroutes2/raw/master/chnroutes.txt
49 |
50 | https://github.com/17mon/china_ip_list/raw/master/china_ip_list.txt
51 |
52 | https://github.com/metowolf/iplist/raw/master/data/special/china.txt
53 | ```
54 |
55 | ### chinalist
56 |
57 | ```yaml
58 | https://github.com/hq450/fancyss/raw/master/rules/WhiteList_new.txt'
59 |
60 | https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/direct-list.txt'
61 |
62 | https://github.com/felixonmars/dnsmasq-china-list/raw/master/accelerated-domains.china.conf'
63 | ```
64 |
65 | ## 下发地址
66 |
67 | > 服务端支持 Gzip 和 Brotli 压缩,纯文本资源下载时启用可显著加快速度。
68 |
69 | FTP 链接:
70 |
71 | + `gfwlist.txt` :`https://res.343.re/Share/cleardns/gfwlist.txt`
72 | + `china-ip.txt` :`https://res.343.re/Share/cleardns/china-ip.txt`
73 | + `chinalist.txt` :`https://res.343.re/Share/cleardns/chinalist.txt`
74 |
75 | > 如果下载工具不支持压缩,可以使用以下预压缩文件,加快下载速度。
76 |
77 | 压缩资源链接:
78 |
79 | + `gfwlist.txt` :`https://res.343.re/Share/cleardns/gfwlist.txt.xz`
80 | + `china-ip.txt` :`https://res.343.re/Share/cleardns/china-ip.txt.xz`
81 | + `chinalist.txt` :`https://res.343.re/Share/cleardns/chinalist.txt.xz`
82 |
83 | > `res.343.re` 为主 FTP 地址,`cdn.dnomd343.top` 为 CDN 镜像地址,国内用户访问更快。
84 |
85 | CDN 下载链接:
86 |
87 | + `gfwlist.txt` :`https://cdn.dnomd343.top/cleardns/gfwlist.txt`
88 | + `china-ip.txt` :`https://cdn.dnomd343.top/cleardns/china-ip.txt`
89 | + `chinalist.txt` :`https://cdn.dnomd343.top/cleardns/chinalist.txt`
90 |
--------------------------------------------------------------------------------
/assets/china-ip.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | import os
5 | from netaddr import IPSet
6 | from netaddr import IPAddress
7 | from netaddr import IPNetwork
8 |
9 | operators = ['china', 'cmcc', 'chinanet', 'unicom', 'tietong', 'cernet', 'cstnet', 'drpeng', 'googlecn']
10 | operators += ['%s6' % x for x in operators] # add `...6` suffix
11 | source = [
12 | 'curl -sL https://github.com/misakaio/chnroutes2/raw/master/chnroutes.txt | sed \'/^#/d\'',
13 | 'curl -sL https://github.com/metowolf/iplist/raw/master/data/special/china.txt',
14 | 'curl -sL https://github.com/17mon/china_ip_list/raw/master/china_ip_list.txt',
15 | ] + ['curl -sL https://gaoyifan.github.io/china-operator-ip/%s.txt' % x for x in operators]
16 |
17 | ipAddrs = set()
18 | for script in source: # traverse fetch commands
19 | raw = os.popen(script).read().split('\n')
20 | ipAddrs.update(filter(None, raw))
21 |
22 | ipv4 = IPSet()
23 | ipv6 = IPSet()
24 | for ipAddr in ipAddrs: # load all IP data
25 | try:
26 | ip = IPNetwork(ipAddr) if '/' in ipAddr else IPAddress(ipAddr)
27 | ipv4.add(ip) if ip.version == 4 else ipv6.add(ip)
28 | except:
29 | pass
30 |
31 | with open('china-ip.txt', 'w') as fileObj: # save to file
32 | fileObj.write('\n'.join([str(ip) for ip in ipv4.iter_cidrs()]) + '\n') # format into CIDR
33 | fileObj.write('\n'.join([str(ip) for ip in ipv6.iter_cidrs()]) + '\n')
34 |
--------------------------------------------------------------------------------
/assets/chinalist.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | import os
5 | import re
6 |
7 | source = [
8 | 'curl -sL https://github.com/felixonmars/dnsmasq-china-list/raw/master/accelerated-domains.china.conf'
9 | ' | sed \'/^#/d\' | sed \'s/server=\\///g;s/\\/114.114.114.114//g\'',
10 | 'curl -sL https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/direct-list.txt'
11 | ' | grep -v \':\'',
12 | 'curl -sL https://github.com/hq450/fancyss/raw/master/rules/WhiteList_new.txt'
13 | ' | sed \'s/Server=\\///g;s/\\///g\'',
14 | ]
15 |
16 | domains = set()
17 | for script in source: # traverse fetch commands
18 | raw = os.popen(script).read().split('\n')
19 | domains.update(filter(None, raw))
20 | regex = r'^(?=^.{3,255}$)[a-zA-Z0-9][a-zA-Z0-9\-]{0,62}(.[a-zA-Z0-9][a-zA-Z0-9\-]{0,62})+$'
21 | domains = {x for x in domains if re.search(regex, str(x)) is not None} # filter invalid domains
22 | with open('chinalist.txt', 'w') as fileObj:
23 | fileObj.write('\n'.join(sorted(domains)) + '\n')
24 |
--------------------------------------------------------------------------------
/assets/gfwlist.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | import os
5 | import re
6 |
7 | source = [
8 | 'curl -sL https://github.com/Loukky/gfwlist-by-loukky/raw/master/gfwlist.txt | base64 -d | '
9 | 'sed \'/^$\\|@@/d\' | sed \'s#!.\\+##;s#|##g;s#@##g;s#http:\\/\\/##;s#https:\\/\\/##;\' | '
10 | 'sed \'/^[0-9]\\+\\.[0-9]\\+\\.[0-9]\\+\\.[0-9]\\+$/d\' | grep \'^[0-9a-zA-Z\\.-]\\+$\' | '
11 | 'grep "\\." | sed \'s#^\\.\\+##\'',
12 | 'curl -sL https://github.com/gfwlist/gfwlist/raw/master/gfwlist.txt | base64 -d | '
13 | 'sed \'/^$\\|@@/d\' | sed \'s#!.\\+##;s#|##g;s#@##g;s#http:\\/\\/##;s#https:\\/\\/##;\' | '
14 | 'sed \'/^[0-9]\\+\\.[0-9]\\+\\.[0-9]\\+\\.[0-9]\\+$/d\' | grep \'^[0-9a-zA-Z\\.-]\\+$\' | '
15 | 'grep "\\." | sed \'s#^\\.\\+##\'',
16 | 'curl -sL https://github.com/hq450/fancyss/raw/master/rules/gfwlist.conf | '
17 | 'sed \'s/ipset=\\/\\.//g;s/\\/gfwlist//g;/^server/d\'',
18 | 'curl -sL https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/gfw.txt',
19 | ]
20 |
21 | domains = set()
22 | for script in source: # traverse fetch commands
23 | raw = os.popen(script).read().split('\n')
24 | domains.update(filter(None, raw))
25 | regex = r'^(?=^.{3,255}$)[a-zA-Z0-9][a-zA-Z0-9\-]{0,62}(.[a-zA-Z0-9][a-zA-Z0-9\-]{0,62})+$'
26 | domains = {x for x in domains if re.search(regex, str(x)) is not None} # filter invalid domains
27 | with open('gfwlist.txt', 'w') as fileObj:
28 | fileObj.write('\n'.join(sorted(domains)) + '\n')
29 |
--------------------------------------------------------------------------------
/include/applet/adguard.h:
--------------------------------------------------------------------------------
1 | #ifndef ADGUARD_H_
2 | #define ADGUARD_H_
3 |
4 | #include
5 | #include "process.h"
6 |
7 | typedef struct {
8 | uint8_t debug; // bool value
9 | uint16_t dns_port;
10 | uint16_t web_port;
11 | char *upstream;
12 | char *username;
13 | char *password;
14 | } adguard;
15 |
16 | adguard* adguard_init();
17 | void adguard_free(adguard *info);
18 | process* adguard_load(adguard *info, const char *dir);
19 |
20 | #endif
21 |
--------------------------------------------------------------------------------
/include/applet/crontab.h:
--------------------------------------------------------------------------------
1 | #ifndef CRONTAB_H_
2 | #define CRONTAB_H_
3 |
4 | #include
5 | #include "process.h"
6 |
7 | typedef struct {
8 | uint8_t debug; // bool value
9 | char *cron; // cron expression
10 | } crontab;
11 |
12 | crontab* crontab_init();
13 | void crontab_free(crontab *info);
14 | process* crontab_load(crontab *info);
15 |
16 | #endif
17 |
--------------------------------------------------------------------------------
/include/applet/dnsproxy.h:
--------------------------------------------------------------------------------
1 | #ifndef DNSPROXY_H_
2 | #define DNSPROXY_H_
3 |
4 | #include
5 | #include "process.h"
6 |
7 | typedef struct {
8 | uint16_t port;
9 | uint32_t cache;
10 | uint8_t ipv6; // bool value
11 | uint8_t debug; // bool value
12 | uint8_t verify; // bool value
13 | uint8_t parallel; // bool value
14 | uint8_t optimistic; // bool value
15 | char **bootstrap;
16 | char **fallback;
17 | char **primary;
18 | } dnsproxy;
19 |
20 | void dnsproxy_free(dnsproxy *info);
21 | dnsproxy* dnsproxy_init(uint16_t port);
22 | process* dnsproxy_load(const char *caption, dnsproxy *info, const char *file);
23 |
24 | #endif
25 |
--------------------------------------------------------------------------------
/include/applet/overture.h:
--------------------------------------------------------------------------------
1 | #ifndef OVERTURE_H_
2 | #define OVERTURE_H_
3 |
4 | #include
5 | #include "process.h"
6 |
7 | typedef struct {
8 | uint16_t port;
9 | uint8_t debug; // bool value
10 | uint32_t timeout;
11 | char *ttl_file;
12 | char *host_file;
13 | uint16_t foreign_port;
14 | uint16_t domestic_port;
15 | uint32_t **reject_type;
16 | char *foreign_ip_file;
17 | char *domestic_ip_file;
18 | char *foreign_domain_file;
19 | char *domestic_domain_file;
20 | } overture;
21 |
22 | overture* overture_init();
23 | void overture_free(overture *info);
24 | process* overture_load(overture *info, const char *file);
25 |
26 | #endif
27 |
--------------------------------------------------------------------------------
/include/bcrypt/bcrypt.h:
--------------------------------------------------------------------------------
1 | #ifndef BCRYPT_H_
2 | #define BCRYPT_H_
3 | /*
4 | * bcrypt wrapper library
5 | *
6 | * Written in 2011, 2013, 2014, 2015 by Ricardo Garcia
7 | *
8 | * To the extent possible under law, the author(s) have dedicated all copyright
9 | * and related and neighboring rights to this software to the public domain
10 | * worldwide. This software is distributed without any warranty.
11 | *
12 | * You should have received a copy of the CC0 Public Domain Dedication along
13 | * with this software. If not, see
14 | * .
15 | */
16 |
17 | #define BCRYPT_HASHSIZE (64)
18 |
19 | #ifdef __cplusplus
20 | extern "C" {
21 | #endif
22 |
23 | /*
24 | * This function expects a work factor between 4 and 31 and a char array to
25 | * store the resulting generated salt. The char array should typically have
26 | * BCRYPT_HASHSIZE bytes at least. If the provided work factor is not in the
27 | * previous range, it will default to 12.
28 | *
29 | * The return value is zero if the salt could be correctly generated and
30 | * nonzero otherwise.
31 | *
32 | */
33 | int bcrypt_gensalt(int workfactor, char salt[BCRYPT_HASHSIZE]);
34 |
35 | /*
36 | * This function expects a password to be hashed, a salt to hash the password
37 | * with and a char array to leave the result. Both the salt and the hash
38 | * parameters should have room for BCRYPT_HASHSIZE characters at least.
39 | *
40 | * It can also be used to verify a hashed password. In that case, provide the
41 | * expected hash in the salt parameter and verify the output hash is the same
42 | * as the input hash. However, to avoid timing attacks, it's better to use
43 | * bcrypt_checkpw when verifying a password.
44 | *
45 | * The return value is zero if the password could be hashed and nonzero
46 | * otherwise.
47 | */
48 | int bcrypt_hashpw(const char *passwd, const char salt[BCRYPT_HASHSIZE],
49 | char hash[BCRYPT_HASHSIZE]);
50 |
51 | /*
52 | * This function expects a password and a hash to verify the password against.
53 | * The internal implementation is tuned to avoid timing attacks.
54 | *
55 | * The return value will be -1 in case of errors, zero if the provided password
56 | * matches the given hash and greater than zero if no errors are found and the
57 | * passwords don't match.
58 | *
59 | */
60 | int bcrypt_checkpw(const char *passwd, const char hash[BCRYPT_HASHSIZE]);
61 |
62 | /*
63 | * This function expects a string and return bcrypt result with random salt.
64 | */
65 |
66 | char* bcrypt_hash(const char *data);
67 |
68 | /*
69 | * This function verifies that the data matches the hash value.
70 | */
71 |
72 | int bcrypt_verify(const char *data, const char *hash);
73 |
74 | /*
75 | * Brief Example
76 | * -------------
77 | *
78 | * Hashing a password:
79 | *
80 | * char salt[BCRYPT_HASHSIZE];
81 | * char hash[BCRYPT_HASHSIZE];
82 | * int ret;
83 | *
84 | * ret = bcrypt_gensalt(12, salt);
85 | * assert(ret == 0);
86 | * ret = bcrypt_hashpw("thepassword", salt, hash);
87 | * assert(ret == 0);
88 | *
89 | *
90 | * Verifying a password:
91 | *
92 | * int ret;
93 | *
94 | * ret = bcrypt_checkpw("thepassword", "expectedhash");
95 | * assert(ret != -1);
96 | *
97 | * if (ret == 0) {
98 | * printf("The password matches\n");
99 | * } else {
100 | * printf("The password does NOT match\n");
101 | * }
102 | *
103 | */
104 |
105 | #ifdef __cplusplus
106 | }
107 | #endif
108 |
109 | #endif
110 |
--------------------------------------------------------------------------------
/include/bcrypt/blowfish/crypt.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Written by Solar Designer in 2000-2002.
3 | * No copyright is claimed, and the software is hereby placed in the public
4 | * domain. In case this attempt to disclaim copyright and place the software
5 | * in the public domain is deemed null and void, then the software is
6 | * Copyright (c) 2000-2002 Solar Designer and it is hereby released to the
7 | * general public under the following terms:
8 | *
9 | * Redistribution and use in source and binary forms, with or without
10 | * modification, are permitted.
11 | *
12 | * There's ABSOLUTELY NO WARRANTY, express or implied.
13 | *
14 | * See crypt_blowfish.c for more information.
15 | */
16 |
17 | #include
18 |
19 | #if defined(_OW_SOURCE) || defined(__USE_OW)
20 | #define __SKIP_GNU
21 | #undef __SKIP_OW
22 | #include
23 | #undef __SKIP_GNU
24 | #endif
25 |
--------------------------------------------------------------------------------
/include/bcrypt/blowfish/crypt_blowfish.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Written by Solar Designer in 2000-2011.
3 | * No copyright is claimed, and the software is hereby placed in the public
4 | * domain. In case this attempt to disclaim copyright and place the software
5 | * in the public domain is deemed null and void, then the software is
6 | * Copyright (c) 2000-2011 Solar Designer and it is hereby released to the
7 | * general public under the following terms:
8 | *
9 | * Redistribution and use in source and binary forms, with or without
10 | * modification, are permitted.
11 | *
12 | * There's ABSOLUTELY NO WARRANTY, express or implied.
13 | *
14 | * See crypt_blowfish.c for more information.
15 | */
16 |
17 | #ifndef _CRYPT_BLOWFISH_H
18 | #define _CRYPT_BLOWFISH_H
19 |
20 | extern int _crypt_output_magic(const char *setting, char *output, int size);
21 | extern char *_crypt_blowfish_rn(const char *key, const char *setting,
22 | char *output, int size);
23 | extern char *_crypt_gensalt_blowfish_rn(const char *prefix,
24 | unsigned long count,
25 | const char *input, int size, char *output, int output_size);
26 |
27 | #endif
28 |
--------------------------------------------------------------------------------
/include/bcrypt/blowfish/crypt_gensalt.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Written by Solar Designer in 2000-2011.
3 | * No copyright is claimed, and the software is hereby placed in the public
4 | * domain. In case this attempt to disclaim copyright and place the software
5 | * in the public domain is deemed null and void, then the software is
6 | * Copyright (c) 2000-2011 Solar Designer and it is hereby released to the
7 | * general public under the following terms:
8 | *
9 | * Redistribution and use in source and binary forms, with or without
10 | * modification, are permitted.
11 | *
12 | * There's ABSOLUTELY NO WARRANTY, express or implied.
13 | *
14 | * See crypt_blowfish.c for more information.
15 | */
16 |
17 | #ifndef _CRYPT_GENSALT_H
18 | #define _CRYPT_GENSALT_H
19 |
20 | extern unsigned char _crypt_itoa64[];
21 | extern char *_crypt_gensalt_traditional_rn(const char *prefix,
22 | unsigned long count,
23 | const char *input, int size, char *output, int output_size);
24 | extern char *_crypt_gensalt_extended_rn(const char *prefix,
25 | unsigned long count,
26 | const char *input, int size, char *output, int output_size);
27 | extern char *_crypt_gensalt_md5_rn(const char *prefix, unsigned long count,
28 | const char *input, int size, char *output, int output_size);
29 |
30 | #endif
31 |
--------------------------------------------------------------------------------
/include/bcrypt/blowfish/ow-crypt.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Written by Solar Designer in 2000-2011.
3 | * No copyright is claimed, and the software is hereby placed in the public
4 | * domain. In case this attempt to disclaim copyright and place the software
5 | * in the public domain is deemed null and void, then the software is
6 | * Copyright (c) 2000-2011 Solar Designer and it is hereby released to the
7 | * general public under the following terms:
8 | *
9 | * Redistribution and use in source and binary forms, with or without
10 | * modification, are permitted.
11 | *
12 | * There's ABSOLUTELY NO WARRANTY, express or implied.
13 | *
14 | * See crypt_blowfish.c for more information.
15 | */
16 |
17 | #ifndef _OW_CRYPT_H
18 | #define _OW_CRYPT_H
19 |
20 | #ifndef __GNUC__
21 | #undef __const
22 | #define __const const
23 | #endif
24 |
25 | #ifndef __SKIP_GNU
26 | extern char *crypt(__const char *key, __const char *setting);
27 | extern char *crypt_r(__const char *key, __const char *setting, void *data);
28 | #endif
29 |
30 | #ifndef __SKIP_OW
31 | extern char *crypt_rn(__const char *key, __const char *setting,
32 | void *data, int size);
33 | extern char *crypt_ra(__const char *key, __const char *setting,
34 | void **data, int *size);
35 | extern char *crypt_gensalt(__const char *prefix, unsigned long count,
36 | __const char *input, int size);
37 | extern char *crypt_gensalt_rn(__const char *prefix, unsigned long count,
38 | __const char *input, int size, char *output, int output_size);
39 | extern char *crypt_gensalt_ra(__const char *prefix, unsigned long count,
40 | __const char *input, int size);
41 | #endif
42 |
43 | #endif
44 |
--------------------------------------------------------------------------------
/include/common/json.h:
--------------------------------------------------------------------------------
1 | #ifndef JSON_H_
2 | #define JSON_H_
3 |
4 | #include
5 | #include "cJSON.h"
6 |
7 | char* to_json_format(const char *content);
8 | uint8_t is_json_suffix(const char *file_name);
9 | cJSON* json_field_get(cJSON *entry, const char *key);
10 | void json_field_replace(cJSON *entry, const char *key, cJSON *content);
11 |
12 | int json_int_value(char *caption, cJSON *json);
13 | uint8_t json_bool_value(char *caption, cJSON *json);
14 | char* json_string_value(char* caption, cJSON *json);
15 | char** json_string_list_value(char *caption, cJSON *json, char **string_list);
16 | uint32_t** json_uint32_list_value(char *caption, cJSON *json, uint32_t **uint32_list);
17 |
18 | #endif
19 |
--------------------------------------------------------------------------------
/include/common/structure.h:
--------------------------------------------------------------------------------
1 | #ifndef STRUCTURE_H_
2 | #define STRUCTURE_H_
3 |
4 | #include
5 |
6 | char** string_list_init();
7 | void string_list_free(char **string_list);
8 | char* string_list_dump(char **string_list);
9 | uint32_t string_list_len(char **string_list);
10 | void string_list_update(char ***base_list, char **update_list);
11 | void string_list_append(char ***string_list, const char *string);
12 |
13 | uint32_t** uint32_list_init();
14 | char* uint32_list_dump(uint32_t **int_list);
15 | void uint32_list_free(uint32_t **uint32_list);
16 | uint32_t uint32_list_len(uint32_t **int_list);
17 | void uint32_list_append(uint32_t ***uint32_list, uint32_t number);
18 | void uint32_list_update(uint32_t ***base_list, uint32_t **update_list);
19 |
20 | #endif
21 |
--------------------------------------------------------------------------------
/include/common/sundry.h:
--------------------------------------------------------------------------------
1 | #ifndef SUNDRY_H_
2 | #define SUNDRY_H_
3 |
4 | #include
5 |
6 | uint8_t check_port(uint16_t port);
7 | const char* show_bool(uint8_t value);
8 | char* string_load(const char *fmt, ...);
9 | char* uint32_to_string(uint32_t number);
10 | char* string_join(const char *base, const char *add);
11 | void string_list_debug(char *describe, char **string_list);
12 | void uint32_list_debug(char *describe, uint32_t **uint32_list);
13 |
14 | #endif
15 |
--------------------------------------------------------------------------------
/include/common/system.h:
--------------------------------------------------------------------------------
1 | #ifndef SYSTEM_H_
2 | #define SYSTEM_H_
3 |
4 | #include
5 |
6 | char* read_file(const char *file);
7 | int run_command(const char *command);
8 | void create_folder(const char *folder);
9 | uint8_t is_file_exist(const char *file);
10 | void save_file(const char *file, const char *content);
11 | void save_string_list(const char *file, char **string_list);
12 | void file_append(const char *base_file, const char *append_file);
13 |
14 | #endif
15 |
--------------------------------------------------------------------------------
/include/constant.h.in:
--------------------------------------------------------------------------------
1 | #ifndef CONSTANT_H_
2 | #define CONSTANT_H_
3 |
4 | #define TRUE 1
5 | #define FALSE 0
6 |
7 | #define DNS_PORT 53
8 | #define ADGUARD_PORT 80
9 | #define DIVERTER_PORT 5353
10 | #define DOMESTIC_PORT 4053
11 | #define FOREIGN_PORT 6053
12 |
13 | #define RESTART_DELAY 1
14 | #define DIVERTER_TIMEOUT 6
15 |
16 | #define VERSION "@VERSION@"
17 | #define CONFIG_FILE "cleardns.yml"
18 |
19 | #define DNSPROXY_BIN "dnsproxy"
20 | #define OVERTURE_BIN "overture"
21 | #define ADGUARD_BIN "AdGuardHome"
22 |
23 | #define EXPOSE_DIR "/cleardns/"
24 | #define WORK_DIR "/etc/cleardns/"
25 | #define ASSETS_DIR "/cleardns/assets/"
26 | #define ADGUARD_DIR "/cleardns/adguard/"
27 |
28 | #define ADGUARD_USER "admin"
29 | #define ADGUARD_PASSWD "cleardns"
30 |
31 | #define UPDATE_CRON "0 4 * * *"
32 | #define ASSETS_PKG "/assets.tar.xz"
33 |
34 | #define ASSET_TTL "ttl.txt"
35 | #define ASSET_HOSTS "hosts.txt"
36 | #define ASSET_GFW_LIST "gfwlist.txt"
37 | #define ASSET_CHINA_IP "china-ip.txt"
38 | #define ASSET_CHINA_LIST "chinalist.txt"
39 |
40 | #define RUST_LOG_INFO 0
41 | #define RUST_LOG_DEBUG 1
42 | #define RUST_LOG_TRACE 2
43 |
44 | #define EXIT_NORMAL 0
45 | #define EXIT_FORK_ERROR 1
46 | #define EXIT_EXEC_ERROR 2
47 | #define EXIT_WAIT_ERROR 3
48 |
49 | #define HELP_MSG "\
50 | ClearDNS usage:\n\
51 | \n\
52 | Command: ./cleardns [OPTIONS]\n\
53 | \n\
54 | Options:\n\
55 | --debug Enable debug mode.\n\
56 | --config Specify config file. (default: cleardns.yml)\n\
57 | --verbose Output verbose log content.\n\
58 | --version Show version information.\n\
59 | --help Print this message.\n\
60 | "
61 |
62 | #endif
63 |
--------------------------------------------------------------------------------
/include/loader/config.h:
--------------------------------------------------------------------------------
1 | #ifndef CONFIG_H_
2 | #define CONFIG_H_
3 |
4 | #include
5 | #include "assets.h"
6 |
7 | typedef struct {
8 | uint16_t port;
9 | uint8_t ipv6; // bool value
10 | uint8_t verify; // bool value
11 | uint8_t parallel; // bool value
12 | char **bootstrap;
13 | char **fallback;
14 | char **primary;
15 | } upstream_config;
16 |
17 | typedef struct {
18 | uint16_t port;
19 | char **gfwlist;
20 | char **china_ip;
21 | char **chinalist;
22 | } diverter_config;
23 |
24 | typedef struct {
25 | uint32_t size;
26 | uint8_t enable; // bool value
27 | uint8_t optimistic; // bool value
28 | } cache_config;
29 |
30 | typedef struct {
31 | uint16_t port;
32 | uint8_t enable;
33 | char *username;
34 | char *password;
35 | } adguard_config;
36 |
37 | typedef struct {
38 | char *cron;
39 | uint8_t disable; // bool value
40 | asset **resources;
41 | } assets_config;
42 |
43 | typedef struct {
44 | uint16_t port;
45 | cache_config cache;
46 | upstream_config domestic;
47 | upstream_config foreign;
48 | diverter_config diverter;
49 | adguard_config adguard;
50 | assets_config assets;
51 | uint32_t **reject;
52 | char **hosts;
53 | char **ttl;
54 | char **script;
55 | } cleardns_config;
56 |
57 | cleardns_config* config_init();
58 | void config_dump(cleardns_config *config);
59 | void config_free(cleardns_config *config);
60 |
61 | #endif
62 |
--------------------------------------------------------------------------------
/include/loader/default.h:
--------------------------------------------------------------------------------
1 | #ifndef DEFAULT_H_
2 | #define DEFAULT_H_
3 |
4 | void load_default_config(const char *file);
5 |
6 | #endif
7 |
--------------------------------------------------------------------------------
/include/loader/loader.h:
--------------------------------------------------------------------------------
1 | #ifndef LOADER_H_
2 | #define LOADER_H_
3 |
4 | #include "config.h"
5 | #include "adguard.h"
6 | #include "dnsproxy.h"
7 | #include "overture.h"
8 | #include "crontab.h"
9 | #include "assets.h"
10 |
11 | struct cleardns {
12 | char **script;
13 | dnsproxy *domestic;
14 | dnsproxy *foreign;
15 | overture *diverter;
16 | adguard *filter;
17 | crontab *crond;
18 | asset **resource;
19 | };
20 |
21 | extern struct cleardns loader;
22 |
23 | void load_diverter_assets();
24 |
25 | void load_config(const char *config_file);
26 |
27 | #endif
28 |
--------------------------------------------------------------------------------
/include/loader/parser.h:
--------------------------------------------------------------------------------
1 | #ifndef PARSER_H_
2 | #define PARSER_H_
3 |
4 | #include "config.h"
5 |
6 | void config_parser(cleardns_config *config, const char *config_file);
7 |
8 | #endif
9 |
--------------------------------------------------------------------------------
/include/to_json.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | /**
9 | * Free the exported c-style string.
10 | */
11 | void free_rust_string(const char *ptr);
12 |
13 | /**
14 | * Format the input text into JSON format and return a c-style string, or return
15 | * `NULL` if an error occurs.
16 | */
17 | const char *to_json(const char *content);
18 |
--------------------------------------------------------------------------------
/include/utils/assets.h:
--------------------------------------------------------------------------------
1 | #ifndef ASSETS_H_
2 | #define ASSETS_H_
3 |
4 | #include
5 |
6 | extern char **custom_gfwlist;
7 | extern char **custom_china_ip;
8 | extern char **custom_chinalist;
9 |
10 | typedef struct {
11 | char *file; // string
12 | char **sources; // string list
13 | } asset;
14 |
15 | void assets_extract();
16 | void assets_load(asset **info);
17 |
18 | asset** assets_init();
19 | asset* asset_init(const char *name);
20 | void assets_dump(asset **asset_list);
21 | void assets_free(asset **asset_list);
22 | uint32_t assets_size(asset **asset_list);
23 | void assets_append(asset ***asset_list, asset *res);
24 |
25 | /// Rust assets interface
26 | void assets_log_init(uint8_t verbose, const char *prefix);
27 | uint8_t asset_update(const char *file, char *const *sources, const char *assets_dir);
28 |
29 | #endif
30 |
--------------------------------------------------------------------------------
/include/utils/cJSON.h:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2009-2017 Dave Gamble and cJSON contributors
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy
5 | of this software and associated documentation files (the "Software"), to deal
6 | in the Software without restriction, including without limitation the rights
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the Software is
9 | furnished to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in
12 | all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | THE SOFTWARE.
21 | */
22 |
23 | #ifndef cJSON__h
24 | #define cJSON__h
25 |
26 | #ifdef __cplusplus
27 | extern "C"
28 | {
29 | #endif
30 |
31 | #if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32))
32 | #define __WINDOWS__
33 | #endif
34 |
35 | #ifdef __WINDOWS__
36 |
37 | /* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 3 define options:
38 |
39 | CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols
40 | CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default)
41 | CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol
42 |
43 | For *nix builds that support visibility attribute, you can define similar behavior by
44 |
45 | setting default visibility to hidden by adding
46 | -fvisibility=hidden (for gcc)
47 | or
48 | -xldscope=hidden (for sun cc)
49 | to CFLAGS
50 |
51 | then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does
52 |
53 | */
54 |
55 | #define CJSON_CDECL __cdecl
56 | #define CJSON_STDCALL __stdcall
57 |
58 | /* export symbols by default, this is necessary for copy pasting the C and header file */
59 | #if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS)
60 | #define CJSON_EXPORT_SYMBOLS
61 | #endif
62 |
63 | #if defined(CJSON_HIDE_SYMBOLS)
64 | #define CJSON_PUBLIC(type) type CJSON_STDCALL
65 | #elif defined(CJSON_EXPORT_SYMBOLS)
66 | #define CJSON_PUBLIC(type) __declspec(dllexport) type CJSON_STDCALL
67 | #elif defined(CJSON_IMPORT_SYMBOLS)
68 | #define CJSON_PUBLIC(type) __declspec(dllimport) type CJSON_STDCALL
69 | #endif
70 | #else /* !__WINDOWS__ */
71 | #define CJSON_CDECL
72 | #define CJSON_STDCALL
73 |
74 | #if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY)
75 | #define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type
76 | #else
77 | #define CJSON_PUBLIC(type) type
78 | #endif
79 | #endif
80 |
81 | /* project version */
82 | #define CJSON_VERSION_MAJOR 1
83 | #define CJSON_VERSION_MINOR 7
84 | #define CJSON_VERSION_PATCH 15
85 |
86 | #include
87 |
88 | /* cJSON Types: */
89 | #define cJSON_Invalid (0)
90 | #define cJSON_False (1 << 0)
91 | #define cJSON_True (1 << 1)
92 | #define cJSON_NULL (1 << 2)
93 | #define cJSON_Number (1 << 3)
94 | #define cJSON_String (1 << 4)
95 | #define cJSON_Array (1 << 5)
96 | #define cJSON_Object (1 << 6)
97 | #define cJSON_Raw (1 << 7) /* raw json */
98 |
99 | #define cJSON_IsReference 256
100 | #define cJSON_StringIsConst 512
101 |
102 | /* The cJSON structure: */
103 | typedef struct cJSON
104 | {
105 | /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */
106 | struct cJSON *next;
107 | struct cJSON *prev;
108 | /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */
109 | struct cJSON *child;
110 |
111 | /* The type of the item, as above. */
112 | int type;
113 |
114 | /* The item's string, if type==cJSON_String and type == cJSON_Raw */
115 | char *valuestring;
116 | /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */
117 | int valueint;
118 | /* The item's number, if type==cJSON_Number */
119 | double valuedouble;
120 |
121 | /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
122 | char *string;
123 | } cJSON;
124 |
125 | typedef struct cJSON_Hooks
126 | {
127 | /* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */
128 | void *(CJSON_CDECL *malloc_fn)(size_t sz);
129 | void (CJSON_CDECL *free_fn)(void *ptr);
130 | } cJSON_Hooks;
131 |
132 | typedef int cJSON_bool;
133 |
134 | /* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them.
135 | * This is to prevent stack overflows. */
136 | #ifndef CJSON_NESTING_LIMIT
137 | #define CJSON_NESTING_LIMIT 1000
138 | #endif
139 |
140 | /* returns the version of cJSON as a string */
141 | CJSON_PUBLIC(const char*) cJSON_Version(void);
142 |
143 | /* Supply malloc, realloc and free functions to cJSON */
144 | CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks);
145 |
146 | /* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */
147 | /* Supply a block of JSON, and this returns a cJSON object you can interrogate. */
148 | CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value);
149 | CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length);
150 | /* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */
151 | /* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */
152 | CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated);
153 | CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated);
154 |
155 | /* Render a cJSON entity to text for transfer/storage. */
156 | CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item);
157 | /* Render a cJSON entity to text for transfer/storage without any formatting. */
158 | CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item);
159 | /* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */
160 | CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt);
161 | /* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */
162 | /* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */
163 | CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format);
164 | /* Delete a cJSON entity and all subentities. */
165 | CJSON_PUBLIC(void) cJSON_Delete(cJSON *item);
166 |
167 | /* Returns the number of items in an array (or object). */
168 | CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array);
169 | /* Retrieve item number "index" from array "array". Returns NULL if unsuccessful. */
170 | CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index);
171 | /* Get item "string" from object. Case insensitive. */
172 | CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string);
173 | CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string);
174 | CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string);
175 | /* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */
176 | CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void);
177 |
178 | /* Check item type and return its value */
179 | CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item);
180 | CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item);
181 |
182 | /* These functions check the type of an item */
183 | CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item);
184 | CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item);
185 | CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item);
186 | CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item);
187 | CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item);
188 | CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item);
189 | CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item);
190 | CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item);
191 | CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item);
192 | CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item);
193 |
194 | /* These calls create a cJSON item of the appropriate type. */
195 | CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void);
196 | CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void);
197 | CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void);
198 | CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean);
199 | CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num);
200 | CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string);
201 | /* raw json */
202 | CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw);
203 | CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void);
204 | CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void);
205 |
206 | /* Create a string where valuestring references a string so
207 | * it will not be freed by cJSON_Delete */
208 | CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string);
209 | /* Create an object/array that only references it's elements so
210 | * they will not be freed by cJSON_Delete */
211 | CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child);
212 | CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child);
213 |
214 | /* These utilities create an Array of count items.
215 | * The parameter count cannot be greater than the number of elements in the number array, otherwise array access will be out of bounds.*/
216 | CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count);
217 | CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count);
218 | CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count);
219 | CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count);
220 |
221 | /* Append item to the specified array/object. */
222 | CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item);
223 | CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item);
224 | /* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object.
225 | * WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before
226 | * writing to `item->string` */
227 | CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item);
228 | /* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */
229 | CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item);
230 | CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item);
231 |
232 | /* Remove/Detach items from Arrays/Objects. */
233 | CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item);
234 | CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which);
235 | CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which);
236 | CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string);
237 | CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string);
238 | CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string);
239 | CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string);
240 |
241 | /* Update array items. */
242 | CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */
243 | CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement);
244 | CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem);
245 | CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem);
246 | CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem);
247 |
248 | /* Duplicate a cJSON item */
249 | CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse);
250 | /* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will
251 | * need to be released. With recurse!=0, it will duplicate any children connected to the item.
252 | * The item->next and ->prev pointers are always zero on return from Duplicate. */
253 | /* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal.
254 | * case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */
255 | CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive);
256 |
257 | /* Minify a strings, remove blank characters(such as ' ', '\t', '\r', '\n') from strings.
258 | * The input pointer json cannot point to a read-only address area, such as a string constant,
259 | * but should point to a readable and writable address area. */
260 | CJSON_PUBLIC(void) cJSON_Minify(char *json);
261 |
262 | /* Helper functions for creating and adding items to an object at the same time.
263 | * They return the added item or NULL on failure. */
264 | CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name);
265 | CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name);
266 | CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name);
267 | CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean);
268 | CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number);
269 | CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string);
270 | CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw);
271 | CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name);
272 | CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name);
273 |
274 | /* When assigning an integer value, it needs to be propagated to valuedouble too. */
275 | #define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number))
276 | /* helper for the cJSON_SetNumberValue macro */
277 | CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number);
278 | #define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number))
279 | /* Change the valuestring of a cJSON_String object, only takes effect when type of object is cJSON_String */
280 | CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring);
281 |
282 | /* Macro for iterating over an array or object */
283 | #define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next)
284 |
285 | /* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */
286 | CJSON_PUBLIC(void *) cJSON_malloc(size_t size);
287 | CJSON_PUBLIC(void) cJSON_free(void *object);
288 |
289 | #ifdef __cplusplus
290 | }
291 | #endif
292 |
293 | #endif
294 |
--------------------------------------------------------------------------------
/include/utils/logger.h:
--------------------------------------------------------------------------------
1 | #ifndef LOGGER_H_
2 | #define LOGGER_H_
3 |
4 | #define LOG_PREFIX "ClearDNS"
5 |
6 | enum {
7 | LOG_DEBUG,
8 | LOG_INFO,
9 | LOG_WARN,
10 | LOG_ERROR,
11 | LOG_FATAL
12 | };
13 |
14 | #define log_debug(...) log_printf(LOG_DEBUG, __VA_ARGS__)
15 | #define log_info(...) log_printf(LOG_INFO, __VA_ARGS__)
16 | #define log_warn(...) log_printf(LOG_WARN, __VA_ARGS__)
17 | #define log_error(...) log_printf(LOG_ERROR, __VA_ARGS__)
18 | #define log_fatal(...) log_printf(LOG_FATAL, __VA_ARGS__)
19 |
20 | extern int LOG_LEVEL;
21 | void log_perror(const char *fmt, ...);
22 | void log_printf(int level, const char *fmt, ...);
23 |
24 | #endif
25 |
--------------------------------------------------------------------------------
/include/utils/process.h:
--------------------------------------------------------------------------------
1 | #ifndef PROCESS_H_
2 | #define PROCESS_H_
3 |
4 | #include
5 |
6 | typedef struct {
7 | int pid; // process id
8 | char *name; // caption
9 | char **cmd; // process command
10 | char **env; // environment variable
11 | char *cwd; // working directory
12 | } process;
13 |
14 | void process_list_run();
15 | void process_list_init();
16 | void process_list_daemon();
17 | void process_list_append(process *proc);
18 | void process_add_arg(process *proc, const char *arg);
19 | process* process_init(const char *caption, const char *bin);
20 |
21 | #endif
22 |
--------------------------------------------------------------------------------
/src/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 2.8.12)
2 |
3 | include_directories(${PROJECT_SOURCE_DIR}/include)
4 | include_directories(${PROJECT_SOURCE_DIR}/include/utils)
5 | include_directories(${PROJECT_SOURCE_DIR}/include/applet)
6 | include_directories(${PROJECT_SOURCE_DIR}/include/bcrypt)
7 | include_directories(${PROJECT_SOURCE_DIR}/include/common)
8 | include_directories(${PROJECT_SOURCE_DIR}/include/loader)
9 |
10 | link_directories(${PROJECT_SOURCE_DIR}/src/target/release)
11 |
12 | configure_file(
13 | ${PROJECT_SOURCE_DIR}/include/constant.h.in
14 | ${PROJECT_SOURCE_DIR}/include/constant.h
15 | )
16 |
17 | add_subdirectory(utils)
18 | add_subdirectory(applet)
19 | add_subdirectory(bcrypt)
20 | add_subdirectory(common)
21 | add_subdirectory(loader)
22 |
23 | add_executable(cleardns cleardns.c)
24 | target_link_libraries(cleardns utils applet bcrypt common loader)
25 |
--------------------------------------------------------------------------------
/src/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 |
3 | resolver = "2"
4 |
5 | members = [
6 | "assets",
7 | "to-json"
8 | ]
9 |
--------------------------------------------------------------------------------
/src/applet/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 2.8.12)
2 |
3 | add_library(applet adguard.c dnsproxy.c overture.c crontab.c)
4 | target_link_libraries(applet bcrypt utils)
5 |
--------------------------------------------------------------------------------
/src/applet/adguard.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include "json.h"
4 | #include "bcrypt.h"
5 | #include "logger.h"
6 | #include "sundry.h"
7 | #include "system.h"
8 | #include "adguard.h"
9 | #include "constant.h"
10 |
11 | void adguard_dump(adguard *info);
12 | char *adguard_config(adguard *info, const char *raw_config);
13 |
14 | void adguard_free(adguard *info) { // free adguard options
15 | free(info->upstream);
16 | free(info->username);
17 | free(info->password);
18 | free(info);
19 | }
20 |
21 | adguard* adguard_init() { // init adguard options
22 | adguard *info = (adguard *)malloc(sizeof(adguard));
23 | char *port_str = uint32_to_string(DIVERTER_PORT); // diverter port in string
24 | info->debug = FALSE;
25 | info->dns_port = DNS_PORT;
26 | info->web_port = ADGUARD_PORT;
27 | info->upstream = string_join("127.0.0.1:", port_str); // default upstream
28 | info->username = strdup(ADGUARD_USER);
29 | info->password = strdup(ADGUARD_PASSWD);
30 | free(port_str);
31 | return info;
32 | }
33 |
34 | void adguard_dump(adguard *info) { // show adguard options in debug log
35 | log_debug("AdGuardHome debug -> %s", show_bool(info->debug));
36 | log_debug("AdGuardHome dns port -> %u", info->dns_port);
37 | log_debug("AdGuardHome web port -> %u", info->web_port);
38 | log_debug("AdGuardHome upstream -> %s", info->upstream);
39 | log_debug("AdGuardHome username -> %s", info->username);
40 | log_debug("AdGuardHome password -> %s", info->password);
41 | }
42 |
43 | char *adguard_config(adguard *info, const char *raw_config) { // modify adguard configure
44 | cJSON *json = cJSON_Parse(raw_config);
45 | if (json == NULL) {
46 | log_fatal("AdGuardHome configure error");
47 | }
48 |
49 | char *password = NULL;
50 | cJSON *user_passwd = cJSON_GetObjectItem(cJSON_GetArrayItem(
51 | cJSON_GetObjectItem(json, "users"), 0), "password");
52 | if (cJSON_IsString(user_passwd)) {
53 | char *hash_val = user_passwd->valuestring;
54 | log_debug("Legacy hash value -> `%s`", hash_val);
55 | if (bcrypt_verify(info->password, hash_val)) {
56 | log_debug("Legacy hash value verify success");
57 | password = strdup(hash_val);
58 | } else {
59 | log_debug("Legacy hash value verify failed");
60 | }
61 | }
62 | if (password == NULL) { // password hash not ready
63 | password = bcrypt_hash(info->password);
64 | }
65 | log_debug("AdGuardHome password -> `%s`", password);
66 |
67 | cJSON *user_config = cJSON_CreateObject(); // setting up username and password
68 | cJSON *users_config = cJSON_CreateArray();
69 | cJSON_AddItemToObject(user_config, "name", cJSON_CreateString(info->username));
70 | cJSON_AddItemToObject(user_config, "password", cJSON_CreateString(password));
71 | cJSON_AddItemToArray(users_config, user_config);
72 | json_field_replace(json, "users", users_config);
73 | free(password);
74 |
75 | cJSON *dns = json_field_get(json, "dns"); // setting up dns options
76 | cJSON *upstream = cJSON_CreateArray();
77 | cJSON_AddItemToArray(upstream, cJSON_CreateString(info->upstream));
78 | json_field_replace(dns, "port", cJSON_CreateNumber(info->dns_port));
79 | json_field_replace(dns, "bind_host", cJSON_CreateString("0.0.0.0"));
80 | json_field_replace(dns, "upstream_dns", upstream);
81 | json_field_replace(dns, "upstream_dns_file", cJSON_CreateString(""));
82 | json_field_replace(dns, "bootstrap_dns", cJSON_CreateArray());
83 |
84 | char *config = cJSON_Print(json); // generate json string
85 | cJSON_free(json);
86 | return config;
87 | }
88 |
89 | process* adguard_load(adguard *info, const char *dir) { // load adguard options
90 | adguard_dump(info);
91 | if (!check_port(info->dns_port)) { // invalid dns port
92 | log_fatal("Invalid dns port `%u`", info->dns_port);
93 | }
94 | if (!check_port(info->web_port)) { // invalid web port
95 | log_fatal("Invalid web port `%u`", info->web_port);
96 | }
97 | if (!strcmp(info->username, "")) { // invalid username
98 | log_fatal("Invalid AdGuardHome username");
99 | }
100 | if (!strcmp(info->password, "")) { // invalid password
101 | log_fatal("Invalid AdGuardHome password");
102 | }
103 |
104 | char *adguard_config_ret;
105 | char *adguard_config_file = string_join(dir, "AdGuardHome.yaml");
106 | create_folder(dir); // ensure adguard work dir exist
107 |
108 | if (!is_file_exist(adguard_config_file)) { // AdGuardHome configure not exist
109 | log_info("Create AdGuardHome configure");
110 | adguard_config_ret = adguard_config(info, "{\"schema_version\": 27}");
111 | } else { // configure exist -> modify
112 | char *adguard_config_content = read_file(adguard_config_file);
113 | char *adguard_config_json = to_json_format(adguard_config_content);
114 | adguard_config_ret = adguard_config(info, adguard_config_json);
115 | free(adguard_config_content);
116 | free(adguard_config_json);
117 | }
118 | save_file(adguard_config_file, adguard_config_ret); // save modified configure
119 | free(adguard_config_file);
120 |
121 | process *proc = process_init("AdGuardHome", ADGUARD_BIN); // generate adguard command
122 | char *port_str = uint32_to_string(info->web_port);
123 | process_add_arg(proc, "--no-check-update"); // disable update check (invalid in docker)
124 | process_add_arg(proc, "--work-dir");
125 | process_add_arg(proc, dir);
126 | process_add_arg(proc, "--port"); // web server port
127 | process_add_arg(proc, port_str);
128 | if (info->debug) {
129 | process_add_arg(proc, "--verbose"); // adguard enable debug mode
130 | }
131 | free(port_str);
132 | log_info("AdGuardHome load success");
133 | return proc;
134 | }
135 |
--------------------------------------------------------------------------------
/src/applet/crontab.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include "logger.h"
5 | #include "sundry.h"
6 | #include "system.h"
7 | #include "crontab.h"
8 | #include "process.h"
9 | #include "constant.h"
10 |
11 | void crontab_dump(crontab *info);
12 |
13 | void crontab_free(crontab *info) { // free crontab options
14 | free(info->cron);
15 | free(info);
16 | }
17 |
18 | crontab* crontab_init() { // init crontab options
19 | crontab *info = (crontab *)malloc(sizeof(crontab));
20 | info->debug = FALSE;
21 | info->cron = strdup(UPDATE_CRON);
22 | return info;
23 | }
24 |
25 | void crontab_dump(crontab *info) { // show crontab options in debug log
26 | log_debug("Crontab debug -> %s", show_bool(info->debug));
27 | log_debug("Crontab expression -> `%s`", info->cron);
28 | }
29 |
30 | process* crontab_load(crontab *info) { // load crontab options
31 | crontab_dump(info);
32 | create_folder("/var/spool/cron/");
33 | create_folder("/var/spool/cron/crontabs/");
34 | char *my_pid = uint32_to_string(getpid());
35 | char *cron_cmd = string_join("\tkill -14 ", my_pid); // SIGALRM -> 14
36 | char *cron_exp = string_join(info->cron, cron_cmd);
37 | save_file("/var/spool/cron/crontabs/root", cron_exp);
38 | free(cron_exp);
39 | free(cron_cmd);
40 | free(my_pid);
41 |
42 | process *proc = process_init("Crontab", "crond");
43 | process_add_arg(proc, "-f"); // foreground
44 | if (info->debug) {
45 | process_add_arg(proc, "-l");
46 | process_add_arg(proc, "0"); // verbose mode
47 | }
48 | return proc;
49 | }
50 |
--------------------------------------------------------------------------------
/src/applet/dnsproxy.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include "cJSON.h"
3 | #include "logger.h"
4 | #include "sundry.h"
5 | #include "system.h"
6 | #include "constant.h"
7 | #include "dnsproxy.h"
8 | #include "structure.h"
9 |
10 | char* dnsproxy_config(dnsproxy *info);
11 | void dnsproxy_dump(const char *caption, dnsproxy *info);
12 |
13 | void dnsproxy_free(dnsproxy *info) { // free dnsproxy options
14 | string_list_free(info->bootstrap);
15 | string_list_free(info->fallback);
16 | string_list_free(info->primary);
17 | free(info);
18 | }
19 |
20 | dnsproxy* dnsproxy_init(uint16_t port) { // init dnsproxy options
21 | dnsproxy *info = (dnsproxy *)malloc(sizeof(dnsproxy));
22 | info->port = port;
23 | info->cache = 0; // disable cache in default
24 | info->ipv6 = TRUE;
25 | info->debug = FALSE;
26 | info->verify = TRUE;
27 | info->parallel = TRUE;
28 | info->optimistic = FALSE;
29 | info->bootstrap = string_list_init();
30 | info->fallback = string_list_init();
31 | info->primary = string_list_init();
32 | return info;
33 | }
34 |
35 | void dnsproxy_dump(const char *caption, dnsproxy *info) { // show dnsproxy options in debug log
36 | char *str_dump;
37 | log_debug("%s port -> %u", caption, info->port);
38 | log_debug("%s cache -> %u", caption, info->cache);
39 | log_debug("%s ipv6 -> %s", caption, show_bool(info->ipv6));
40 | log_debug("%s debug -> %s", caption, show_bool(info->debug));
41 | log_debug("%s verify -> %s", caption, show_bool(info->verify));
42 | log_debug("%s parallel -> %s", caption, show_bool(info->parallel));
43 | log_debug("%s optimistic -> %s", caption, show_bool(info->optimistic));
44 |
45 | str_dump = string_list_dump(info->bootstrap);
46 | log_debug("%s bootstrap -> %s", caption, str_dump);
47 | free(str_dump);
48 |
49 | str_dump = string_list_dump(info->fallback);
50 | log_debug("%s fallback -> %s", caption, str_dump);
51 | free(str_dump);
52 |
53 | str_dump = string_list_dump(info->primary);
54 | log_debug("%s primary -> %s", caption, str_dump);
55 | free(str_dump);
56 | }
57 |
58 | process* dnsproxy_load(const char *caption, dnsproxy *info, const char *file) { // load dnsproxy options
59 | dnsproxy_dump(caption, info);
60 | if (!check_port(info->port)) { // invalid server port
61 | log_fatal("Invalid port `%u`", info->port);
62 | }
63 | if (string_list_len(info->primary) == 0) { // without primary dns server
64 | log_fatal("%s without primary dns server", caption);
65 | }
66 |
67 | char *config = dnsproxy_config(info); // string config with json format
68 | char *config_file = string_join(WORK_DIR, file);
69 | save_file(config_file, config); // load dnsproxy configure
70 | free(config_file);
71 | free(config);
72 |
73 | char *option = string_join("--config-path=", file);
74 | process *proc = process_init(caption, DNSPROXY_BIN); // generate dnsproxy command
75 | process_add_arg(proc, option);
76 | if (info->debug) {
77 | process_add_arg(proc, "--verbose"); // dnsproxy enable debug mode
78 | }
79 | free(option);
80 | log_info("%s load success", caption);
81 | return proc;
82 | }
83 |
84 | char* dnsproxy_config(dnsproxy *info) { // generate json configure from dnsproxy options
85 | cJSON *config = cJSON_CreateObject();
86 | if (!info->verify) {
87 | cJSON_AddTrueToObject(config, "insecure"); // insecure --(default)--> `false`
88 | }
89 | if (info->parallel) {
90 | cJSON_AddTrueToObject(config, "all-servers"); // all-servers --(default)--> `false`
91 | }
92 | if (info->cache) {
93 | cJSON_AddTrueToObject(config, "cache"); // cache --(default)--> `false`
94 | cJSON_AddNumberToObject(config, "cache-size", info->cache);
95 | }
96 | if (info->optimistic) {
97 | cJSON_AddTrueToObject(config, "cache-optimistic"); // cache-optimistic --(default)--> `false`
98 | }
99 | if (!info->ipv6) {
100 | cJSON_AddTrueToObject(config, "ipv6-disabled"); // ipv6-disabled --(default)--> `false`
101 | }
102 |
103 | cJSON *port = cJSON_CreateArray();
104 | cJSON_AddItemToArray(port, cJSON_CreateNumber(info->port));
105 | cJSON_AddItemToObject(config, "listen-ports", port); // listen-ports <=> port
106 |
107 | cJSON *bootstrap = cJSON_CreateArray();
108 | for (char **server = info->bootstrap; *server != NULL; ++server) {
109 | cJSON_AddItemToArray(bootstrap, cJSON_CreateString(*server));
110 | }
111 | cJSON_AddItemToObject(config, "bootstrap", bootstrap); // bootstrap <=> bootstrap
112 |
113 | cJSON *fallback = cJSON_CreateArray();
114 | for (char **server = info->fallback; *server != NULL; ++server) {
115 | cJSON_AddItemToArray(fallback, cJSON_CreateString(*server));
116 | }
117 | cJSON_AddItemToObject(config, "fallback", fallback); // fallback <=> fallback
118 |
119 | cJSON *primary = cJSON_CreateArray();
120 | for (char **server = info->primary; *server != NULL; ++server) {
121 | cJSON_AddItemToArray(primary, cJSON_CreateString(*server));
122 | }
123 | cJSON_AddItemToObject(config, "upstream", primary); // upstream <=> primary
124 |
125 | char *config_str = cJSON_Print(config);
126 | cJSON_Delete(config); // free json object
127 | return config_str;
128 | }
129 |
--------------------------------------------------------------------------------
/src/applet/overture.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include "cJSON.h"
4 | #include "logger.h"
5 | #include "sundry.h"
6 | #include "system.h"
7 | #include "overture.h"
8 | #include "constant.h"
9 | #include "structure.h"
10 |
11 | void overture_dump(overture *info);
12 | char* overture_config(overture *info);
13 |
14 | void overture_free(overture *info) { // free overture options
15 | free(info->ttl_file); // free(NULL) is valid
16 | free(info->host_file);
17 | free(info->foreign_ip_file);
18 | free(info->domestic_ip_file);
19 | free(info->foreign_domain_file);
20 | free(info->domestic_domain_file);
21 | free(info);
22 | }
23 |
24 | overture* overture_init() { // init overture options
25 | overture *info = (overture *)malloc(sizeof(overture));
26 | info->port = DIVERTER_PORT;
27 | info->debug = FALSE;
28 | info->timeout = DIVERTER_TIMEOUT; // default timeout
29 | info->ttl_file = NULL;
30 | info->host_file = NULL;
31 | info->foreign_port = FOREIGN_PORT;
32 | info->domestic_port = DOMESTIC_PORT;
33 | info->reject_type = uint32_list_init();
34 | info->foreign_ip_file = strdup("/dev/null");
35 | info->domestic_ip_file = strdup("/dev/null");
36 | info->foreign_domain_file = strdup("/dev/null");
37 | info->domestic_domain_file = strdup("/dev/null");
38 | return info;
39 | }
40 |
41 | void overture_dump(overture *info) { // show overture options in debug log
42 | char *reject_type = uint32_list_dump(info->reject_type);
43 | log_debug("Overture port -> %u", info->port);
44 | log_debug("Overture debug -> %s", show_bool(info->debug));
45 | log_debug("Overture timeout -> %u", info->timeout);
46 | log_debug("Overture ttl file -> %s", info->ttl_file);
47 | log_debug("Overture host file -> %s", info->host_file);
48 | log_debug("Overture foreign port -> %u", info->foreign_port);
49 | log_debug("Overture domestic port -> %u", info->domestic_port);
50 | log_debug("Overture reject type -> %s", reject_type);
51 | log_debug("Overture foreign ip file -> %s", info->foreign_ip_file);
52 | log_debug("Overture domestic ip file -> %s", info->domestic_ip_file);
53 | log_debug("Overture foreign domain file -> %s", info->foreign_domain_file);
54 | log_debug("Overture domestic domain file -> %s", info->domestic_domain_file);
55 | free(reject_type);
56 | }
57 |
58 | process* overture_load(overture *info, const char *file) { // load overture options
59 | overture_dump(info);
60 | if (!check_port(info->port)) { // invalid server port
61 | log_fatal("Invalid port `%u`", info->port);
62 | }
63 | if (info->timeout == 0) { // invalid timeout value
64 | log_fatal("Timeout of overture with invalid value 0");
65 | }
66 |
67 | char *config = overture_config(info); // string config (JSON format)
68 | char *config_file = string_join(WORK_DIR, file);
69 | save_file(config_file, config);
70 | free(config_file);
71 | free(config);
72 |
73 | process *proc = process_init("Overture", OVERTURE_BIN); // generate overture command
74 | process_add_arg(proc, "-c");
75 | process_add_arg(proc, file);
76 | if (info->debug) {
77 | process_add_arg(proc, "-v"); // overture enable debug mode
78 | }
79 | log_info("Overture load success");
80 | return proc;
81 | }
82 |
83 | char* overture_config(overture *info) { // generate json configure from overture options
84 | char *port_str;
85 | cJSON *config = cJSON_CreateObject();
86 |
87 | port_str = uint32_to_string(info->port);
88 | char *bind_addr = string_join(":", port_str);
89 | cJSON_AddStringToObject(config, "bindAddress", bind_addr);
90 | free(bind_addr);
91 | free(port_str);
92 |
93 | cJSON_AddFalseToObject(config, "onlyPrimaryDNS");
94 | cJSON_AddFalseToObject(config, "ipv6UseAlternativeDNS");
95 | cJSON_AddTrueToObject(config, "alternativeDNSConcurrent");
96 | cJSON_AddStringToObject(config, "whenPrimaryDNSAnswerNoneUse", "alternativeDNS");
97 |
98 | cJSON *primary = cJSON_CreateObject();
99 | cJSON *primary_dns = cJSON_CreateArray();
100 | cJSON *primary_edns = cJSON_CreateObject();
101 | port_str = uint32_to_string(info->domestic_port);
102 | char *domestic_port = string_join("127.0.0.1:", port_str);
103 | cJSON_AddStringToObject(primary, "name", "Domestic");
104 | cJSON_AddStringToObject(primary, "address", domestic_port);
105 | cJSON_AddStringToObject(primary, "protocol", "udp");
106 | cJSON_AddNumberToObject(primary, "timeout", info->timeout);
107 | cJSON_AddStringToObject(primary_edns, "policy", "disable");
108 | cJSON_AddItemToObject(primary, "ednsClientSubnet", primary_edns);
109 | cJSON_AddItemToArray(primary_dns, primary);
110 | cJSON_AddItemToObject(config, "primaryDNS", primary_dns);
111 | free(domestic_port);
112 | free(port_str);
113 |
114 | cJSON *alternative = cJSON_CreateObject();
115 | cJSON *alternative_dns = cJSON_CreateArray();
116 | cJSON *alternative_edns = cJSON_CreateObject();
117 | port_str = uint32_to_string(info->foreign_port);
118 | char *foreign_addr = string_join("127.0.0.1:", port_str);
119 | cJSON_AddStringToObject(alternative, "name", "Foreign");
120 | cJSON_AddStringToObject(alternative, "address", foreign_addr);
121 | cJSON_AddStringToObject(alternative, "protocol", "udp");
122 | cJSON_AddNumberToObject(alternative, "timeout", info->timeout);
123 | cJSON_AddStringToObject(alternative_edns, "policy", "disable");
124 | cJSON_AddItemToObject(alternative, "ednsClientSubnet", alternative_edns);
125 | cJSON_AddItemToArray(alternative_dns, alternative);
126 | cJSON_AddItemToObject(config, "alternativeDNS", alternative_dns);
127 | free(foreign_addr);
128 | free(port_str);
129 |
130 | cJSON *ip_file = cJSON_CreateObject();
131 | cJSON_AddStringToObject(ip_file, "primary", info->domestic_ip_file);
132 | cJSON_AddStringToObject(ip_file, "alternative", info->foreign_ip_file);
133 | cJSON_AddItemToObject(config, "ipNetworkFile", ip_file);
134 |
135 | cJSON *domain_file = cJSON_CreateObject();
136 | cJSON_AddStringToObject(domain_file, "primary", info->domestic_domain_file);
137 | cJSON_AddStringToObject(domain_file, "alternative", info->foreign_domain_file);
138 | cJSON_AddStringToObject(domain_file, "matcher", "suffix-tree");
139 | cJSON_AddItemToObject(config, "domainFile", domain_file);
140 |
141 | cJSON *host_file = cJSON_CreateObject();
142 | if (info->host_file != NULL) {
143 | cJSON_AddStringToObject(host_file, "hostsFile", info->host_file);
144 | }
145 | cJSON_AddStringToObject(host_file, "finder", "regex-list");
146 | cJSON_AddItemToObject(config, "hostsFile", host_file);
147 | if (info->ttl_file != NULL) {
148 | cJSON_AddStringToObject(config, "domainTTLFile", info->ttl_file);
149 | }
150 |
151 | if (uint32_list_len(info->reject_type)) {
152 | cJSON *reject_type = cJSON_CreateArray();
153 | for (uint32_t **rr_num = info->reject_type; *rr_num != NULL; ++rr_num) {
154 | cJSON_AddItemToArray(reject_type, cJSON_CreateNumber(**rr_num));
155 | }
156 | cJSON_AddItemToObject(config, "rejectQType", reject_type);
157 | }
158 |
159 | char *config_str = cJSON_Print(config);
160 | cJSON_Delete(config); // free json object
161 | return config_str;
162 | }
163 |
--------------------------------------------------------------------------------
/src/assets/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "assets"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [lib]
7 | crate-type = ["staticlib"]
8 |
9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
10 |
11 | [dependencies]
12 | chrono = "0.4.40"
13 | env_logger = "0.11.8"
14 | log = "0.4.27"
15 | reqwest = { version = "0.11.27", default-features = false, features = ["rustls-tls", "deflate", "gzip", "brotli"] }
16 | reqwest-middleware = "0.2.5"
17 | reqwest-retry = "0.2.3"
18 | tokio = { version = "1.44.2", features = ["macros", "rt-multi-thread"] }
19 |
--------------------------------------------------------------------------------
/src/assets/cbindgen.toml:
--------------------------------------------------------------------------------
1 | language = "C"
2 | pragma_once = true
3 | include_version = false
4 |
--------------------------------------------------------------------------------
/src/assets/src/fetch.rs:
--------------------------------------------------------------------------------
1 | use std::fs::File;
2 | use std::io::Read;
3 | use std::time::Duration;
4 | use log::{debug, info, warn};
5 | use std::collections::HashSet;
6 | use reqwest_middleware::ClientBuilder;
7 | use reqwest_retry::RetryTransientMiddleware;
8 | use reqwest_retry::policies::ExponentialBackoff;
9 |
10 | /// Http download timeout limit
11 | const TIMEOUT: u64 = 120;
12 |
13 | /// Cut text line by line and remove invisible characters on both sides.
14 | fn asset_tidy(data: &str) -> Vec {
15 | data.lines()
16 | .map(|x| String::from(x.trim()))
17 | .filter(|x| !x.is_empty())
18 | .collect()
19 | }
20 |
21 | /// Remove duplicate elements from an array.
22 | fn remove_dup(data: &Vec) -> Vec {
23 | let mut result: Vec = vec![];
24 | let mut tmp: HashSet = HashSet::new();
25 | for val in data {
26 | if tmp.insert(val.clone()) { // value already exist
27 | result.push(val.clone());
28 | }
29 | }
30 | result
31 | }
32 |
33 | /// Download the specified text file and organize it into a String array.
34 | async fn http_fetch(url: &str, timeout: u64) -> Result, String> {
35 | let retry_policy = ExponentialBackoff::builder()
36 | .backoff_exponent(2) // [2, 4, 8, 16, ...]
37 | .retry_bounds(
38 | Duration::from_secs(5), // min retry interval -> 1s
39 | Duration::from_secs(60)) // max retry interval -> 60s
40 | .build_with_max_retries(2); // total request 3 times
41 | let client = ClientBuilder::new(reqwest::Client::new())
42 | .with(RetryTransientMiddleware::new_with_policy(retry_policy))
43 | .build();
44 | info!("Start downloading `{}`", url);
45 | match client.get(url)
46 | .timeout(Duration::from_secs(timeout))
47 | .send().await {
48 | Ok(response) => {
49 | match response.text().await {
50 | Ok(text) => {
51 | debug!("Remote file `{}` download success", url);
52 | Ok(asset_tidy(&text))
53 | },
54 | Err(err) => Err(format!("http content error: {}", err))
55 | }
56 | },
57 | Err(err) => Err(format!("http request failed: {}", err))
58 | }
59 | }
60 |
61 | /// Read the specified text file and organize it into a String array.
62 | async fn local_fetch(path: &str) -> Result, String> {
63 | match File::open(path) {
64 | Ok(mut file) => {
65 | let mut text = String::new();
66 | if let Err(err) = file.read_to_string(&mut text) {
67 | return Err(format!("file `{}` read failed: {}", path, err));
68 | };
69 | debug!("Local file `{}` read success", path);
70 | Ok(asset_tidy(&text))
71 | },
72 | Err(err) => Err(format!("file `{}` open failed: {}", path, err)),
73 | }
74 | }
75 |
76 | /// Get multiple resource data and merge them.
77 | pub(crate) async fn asset_fetch(name: &str, sources: &Vec) -> Option> {
78 | let is_remote = |src: &str| {
79 | src.starts_with("http://") || src.starts_with("https://")
80 | };
81 | let mut contents: Vec> = vec![];
82 | for source in sources {
83 | contents.push(match if is_remote(&source) {
84 | http_fetch(source.trim(), TIMEOUT).await // from remote text file
85 | } else {
86 | local_fetch(source.trim()).await // from local text file
87 | } {
88 | Ok(data) => {
89 | debug!("Asset source `{}` fetch success with {} items", source.trim(), data.len());
90 | data
91 | },
92 | Err(err) => {
93 | warn!("Asset source `{}` fetch failed: {}", source.trim(), err);
94 | return None; // stop fetch process
95 | }
96 | });
97 | }
98 | let contents = remove_dup(&contents
99 | .into_iter()
100 | .flatten()
101 | .collect::>());
102 | info!("Asset `{}` fetch complete with {} items", name, contents.len());
103 | Some(contents)
104 | }
105 |
106 | #[cfg(test)]
107 | mod tests {
108 | use std::fs;
109 | use std::pin::Pin;
110 | use std::future::Future;
111 | use std::fs::OpenOptions;
112 | use std::io::Write;
113 | use super::{asset_tidy, remove_dup};
114 | use super::{http_fetch, local_fetch, asset_fetch};
115 |
116 | const TEST_DATA: &str = "\tabc \n123 \n 456\r\nabc\n\n789 ";
117 |
118 | #[test]
119 | fn basic() {
120 | assert_eq!(asset_tidy(TEST_DATA), vec![
121 | String::from("abc"),
122 | String::from("123"),
123 | String::from("456"),
124 | String::from("abc"),
125 | String::from("789"),
126 | ]);
127 | assert_eq!(remove_dup(&asset_tidy(TEST_DATA)), vec![
128 | String::from("abc"),
129 | String::from("123"),
130 | String::from("456"),
131 | String::from("789"),
132 | ]);
133 | }
134 |
135 | fn run_async(func: Pin>>) {
136 | tokio::runtime::Builder::new_multi_thread()
137 | .enable_all()
138 | .build()
139 | .unwrap()
140 | .block_on(func);
141 | }
142 |
143 | fn gen_test_file() {
144 | let mut fp = OpenOptions::new()
145 | .write(true)
146 | .create(true)
147 | .truncate(true)
148 | .open("/tmp/assets_test_file")
149 | .unwrap();
150 | fp.write_all(TEST_DATA.as_ref()).expect("test file create error");
151 | }
152 |
153 | #[test]
154 | fn asset() {
155 | // test http_fetch function
156 | run_async(Box::pin(async {
157 | assert!(http_fetch("invalid url", 10).await.is_err());
158 | assert_eq!(
159 | http_fetch("https://gstatic.com/generate_204", 10).await,
160 | Ok(vec![])
161 | );
162 | }));
163 |
164 | // test local_fetch function
165 | gen_test_file();
166 | run_async(Box::pin(async {
167 | assert!(local_fetch("/").await.is_err());
168 | assert_eq!(
169 | local_fetch("/tmp/assets_test_file").await,
170 | Ok(vec![
171 | String::from("abc"),
172 | String::from("123"),
173 | String::from("456"),
174 | String::from("abc"),
175 | String::from("789"),
176 | ])
177 | );
178 | }));
179 |
180 | // test combine asset_fetch function
181 | run_async(Box::pin(async {
182 | assert!(
183 | asset_fetch("", &vec![]).await.unwrap().is_empty()
184 | );
185 | assert!(
186 | asset_fetch("", &vec![String::from("...")]).await.is_none()
187 | );
188 | assert_eq!(
189 | asset_fetch("", &vec![
190 | String::from("/tmp/assets_test_file"),
191 | String::from("https://gstatic.com/generate_204")
192 | ]).await,
193 | Some(vec![
194 | String::from("abc"),
195 | String::from("123"),
196 | String::from("456"),
197 | String::from("789"),
198 | ])
199 | );
200 | }));
201 | fs::remove_file("/tmp/assets_test_file").expect("test file delete error");
202 | }
203 | }
204 |
--------------------------------------------------------------------------------
/src/assets/src/ffi.rs:
--------------------------------------------------------------------------------
1 | use std::io::Write;
2 | use std::env::set_var;
3 | use log::{debug, warn};
4 | use std::path::PathBuf;
5 | use std::fs::OpenOptions;
6 | use std::os::raw::c_char;
7 | use std::ffi::{CStr, CString};
8 | use crate::fetch::asset_fetch;
9 |
10 | /// Compatible with C89 bool value.
11 | const TRUE: u8 = 1;
12 | const FALSE: u8 = 0;
13 |
14 | /// Load c-style string from `char *` pointer.
15 | unsafe fn load_c_string(ptr: *const c_char) -> String {
16 | CString::from(CStr::from_ptr(ptr))
17 | .into_string()
18 | .unwrap()
19 | }
20 |
21 | /// Load c-style string list from `char **` pointer.
22 | unsafe fn load_c_string_list(ptr: *const *const c_char) -> Vec {
23 | let mut index = 0;
24 | let mut string_list: Vec = vec![];
25 | while *ptr.offset(index) != std::ptr::null() { // traverse until `NULL`
26 | string_list.push(load_c_string(*ptr.offset(index)));
27 | index += 1;
28 | }
29 | string_list
30 | }
31 |
32 | /// Initialize the rust module log, using info level log when verbose is `0`,
33 | /// enable debug level when verbose is `1`, and others for trace level.
34 | #[no_mangle]
35 | pub unsafe extern "C" fn assets_log_init(verbose: u8, prefix: *const c_char) {
36 | if verbose == 0 { // info level
37 | set_var("RUST_LOG", "info");
38 | } else if verbose == 1 { // debug level
39 | set_var("RUST_LOG", "debug");
40 | } else { // trace level
41 | set_var("RUST_LOG", "trace");
42 | }
43 | let log_prefix = load_c_string(prefix);
44 | env_logger::builder()
45 | .target(env_logger::Target::Stderr)
46 | .format(move |buf, record| {
47 | let prefix = format!(
48 | "\x1b[36m[{}]\x1b[0m \x1b[90m{}\x1b[0m \x1b[35m[{}]\x1b[0m",
49 | log_prefix,
50 | chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f").to_string(),
51 | record.module_path().unwrap()
52 | );
53 | let level = format!("{}[{}]\x1b[0m", match record.level() {
54 | log::Level::Trace => "\x1b[39m",
55 | log::Level::Debug => "\x1b[36m",
56 | log::Level::Info => "\x1b[32m",
57 | log::Level::Warn => "\x1b[33m",
58 | log::Level::Error => "\x1b[31m",
59 | }, record.level());
60 | write!(buf, "{} {} {:?}\n", prefix, level, record.args())
61 | })
62 | .init();
63 | }
64 |
65 | /// Update the specified resource file, return `0` on failure.
66 | #[no_mangle]
67 | #[tokio::main]
68 | pub async unsafe extern "C" fn asset_update(
69 | file: *const c_char, sources: *const *const c_char, assets_dir: *const c_char) -> u8 {
70 |
71 | let name = load_c_string(file); // import c-style string
72 | let sources = load_c_string_list(sources);
73 | let assets_dir = load_c_string(assets_dir);
74 | debug!("Working folder is `{}`", assets_dir);
75 | debug!("Updating `{}` from {:?}", name, sources);
76 |
77 | let assets_dir = PathBuf::from(&assets_dir);
78 | let is_remote = |src: &str| {
79 | src.starts_with("http://") || src.starts_with("https://")
80 | };
81 | let sources = sources.iter()
82 | .map(|src| {
83 | if !is_remote(&src) && !src.starts_with("/") { // local relative path
84 | let file_path = assets_dir.join(src);
85 | String::from(file_path.to_str().unwrap())
86 | } else {
87 | src.clone()
88 | }
89 | })
90 | .collect::>();
91 | let file = String::from(assets_dir.join(&name).to_str().unwrap());
92 | debug!("Asset sources -> {:?}", sources);
93 | debug!("Asset target -> `{}`", file);
94 |
95 | match asset_fetch(&name, &sources).await {
96 | Some(data) => {
97 | let mut content = String::new();
98 | let _ = data.iter().map(|item| {
99 | content.push_str(item);
100 | content.push('\n');
101 | }).collect::>();
102 | match OpenOptions::new()
103 | .write(true)
104 | .create(true)
105 | .truncate(true)
106 | .open(&file) { // open target file
107 | Ok(mut fp) => {
108 | match fp.write_all(content.as_ref()) {
109 | Err(err) => warn!("File `{}` save error: {}", file, err),
110 | _ => debug!("File `{}` save success", file),
111 | }
112 | },
113 | Err(err) => warn!("File `{}` open failed: {}", file, err),
114 | };
115 | TRUE // asset fetch success
116 | },
117 | None => FALSE, // asset fetch failed
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/assets/src/lib.rs:
--------------------------------------------------------------------------------
1 | mod ffi;
2 | mod fetch;
3 |
--------------------------------------------------------------------------------
/src/bcrypt/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 2.8.12)
2 |
3 | include_directories(${PROJECT_SOURCE_DIR}/include/bcrypt/blowfish)
4 |
5 | add_subdirectory(blowfish)
6 |
7 | add_library(bcrypt bcrypt.c hash.c)
8 | target_link_libraries(bcrypt blowfish)
9 |
--------------------------------------------------------------------------------
/src/bcrypt/bcrypt.c:
--------------------------------------------------------------------------------
1 | /*
2 | * bcrypt wrapper library
3 | *
4 | * Written in 2011, 2013, 2014, 2015 by Ricardo Garcia
5 | *
6 | * To the extent possible under law, the author(s) have dedicated all copyright
7 | * and related and neighboring rights to this software to the public domain
8 | * worldwide. This software is distributed without any warranty.
9 | *
10 | * You should have received a copy of the CC0 Public Domain Dedication along
11 | * with this software. If not, see
12 | * .
13 | */
14 | #include
15 | #include
16 | #include
17 | #include
18 | #include
19 | #include
20 |
21 | #include "bcrypt.h"
22 | #include "ow-crypt.h"
23 |
24 | #define RANDBYTES (16)
25 |
26 | static int try_close(int fd)
27 | {
28 | int ret;
29 | for (;;) {
30 | errno = 0;
31 | ret = close(fd);
32 | if (ret == -1 && errno == EINTR)
33 | continue;
34 | break;
35 | }
36 | return ret;
37 | }
38 |
39 | static int try_read(int fd, char *out, size_t count)
40 | {
41 | size_t total;
42 | ssize_t partial;
43 |
44 | total = 0;
45 | while (total < count)
46 | {
47 | for (;;) {
48 | errno = 0;
49 | partial = read(fd, out + total, count - total);
50 | if (partial == -1 && errno == EINTR)
51 | continue;
52 | break;
53 | }
54 |
55 | if (partial < 1)
56 | return -1;
57 |
58 | total += partial;
59 | }
60 |
61 | return 0;
62 | }
63 |
64 | /*
65 | * This is a best effort implementation. Nothing prevents a compiler from
66 | * optimizing this function and making it vulnerable to timing attacks, but
67 | * this method is commonly used in crypto libraries like NaCl.
68 | *
69 | * Return value is zero if both strings are equal and nonzero otherwise.
70 | */
71 | static int timing_safe_strcmp(const char *str1, const char *str2)
72 | {
73 | const unsigned char *u1;
74 | const unsigned char *u2;
75 | int ret;
76 | int i;
77 |
78 | int len1 = strlen(str1);
79 | int len2 = strlen(str2);
80 |
81 | /* In our context both strings should always have the same length
82 | * because they will be hashed passwords. */
83 | if (len1 != len2)
84 | return 1;
85 |
86 | /* Force unsigned for bitwise operations. */
87 | u1 = (const unsigned char *)str1;
88 | u2 = (const unsigned char *)str2;
89 |
90 | ret = 0;
91 | for (i = 0; i < len1; ++i)
92 | ret |= (u1[i] ^ u2[i]);
93 |
94 | return ret;
95 | }
96 |
97 | int bcrypt_gensalt(int factor, char salt[BCRYPT_HASHSIZE])
98 | {
99 | int fd;
100 | char input[RANDBYTES];
101 | int workf;
102 | char *aux;
103 |
104 | fd = open("/dev/urandom", O_RDONLY);
105 | if (fd == -1)
106 | return 1;
107 |
108 | if (try_read(fd, input, RANDBYTES) != 0) {
109 | if (try_close(fd) != 0)
110 | return 4;
111 | return 2;
112 | }
113 |
114 | if (try_close(fd) != 0)
115 | return 3;
116 |
117 | /* Generate salt. */
118 | workf = (factor < 4 || factor > 31)?12:factor;
119 | aux = crypt_gensalt_rn("$2a$", workf, input, RANDBYTES,
120 | salt, BCRYPT_HASHSIZE);
121 | return (aux == NULL)?5:0;
122 | }
123 |
124 | int bcrypt_hashpw(const char *passwd, const char salt[BCRYPT_HASHSIZE], char hash[BCRYPT_HASHSIZE])
125 | {
126 | char *aux;
127 | aux = crypt_rn(passwd, salt, hash, BCRYPT_HASHSIZE);
128 | return (aux == NULL)?1:0;
129 | }
130 |
131 | int bcrypt_checkpw(const char *passwd, const char hash[BCRYPT_HASHSIZE])
132 | {
133 | int ret;
134 | char outhash[BCRYPT_HASHSIZE];
135 |
136 | ret = bcrypt_hashpw(passwd, hash, outhash);
137 | if (ret != 0)
138 | return -1;
139 |
140 | return timing_safe_strcmp(hash, outhash);
141 | }
142 |
143 | #ifdef TEST_BCRYPT
144 | #include
145 | #include
146 | #include
147 | #include
148 |
149 | int main(void)
150 | {
151 | clock_t before;
152 | clock_t after;
153 | char salt[BCRYPT_HASHSIZE];
154 | char hash[BCRYPT_HASHSIZE];
155 | int ret;
156 |
157 | const char pass[] = "hi,mom";
158 | const char hash1[] = "$2a$10$VEVmGHy4F4XQMJ3eOZJAUeb.MedU0W10pTPCuf53eHdKJPiSE8sMK";
159 | const char hash2[] = "$2a$10$3F0BVk5t8/aoS.3ddaB3l.fxg5qvafQ9NybxcpXLzMeAt.nVWn.NO";
160 |
161 | ret = bcrypt_gensalt(12, salt);
162 | assert(ret == 0);
163 | printf("Generated salt: %s\n", salt);
164 | before = clock();
165 | ret = bcrypt_hashpw("testtesttest", salt, hash);
166 | assert(ret == 0);
167 | after = clock();
168 | printf("Hashed password: %s\n", hash);
169 | printf("Time taken: %f seconds\n",
170 | (double)(after - before) / CLOCKS_PER_SEC);
171 |
172 | ret = bcrypt_hashpw(pass, hash1, hash);
173 | assert(ret == 0);
174 | printf("First hash check: %s\n", (strcmp(hash1, hash) == 0)?"OK":"FAIL");
175 | ret = bcrypt_hashpw(pass, hash2, hash);
176 | assert(ret == 0);
177 | printf("Second hash check: %s\n", (strcmp(hash2, hash) == 0)?"OK":"FAIL");
178 |
179 | before = clock();
180 | ret = (bcrypt_checkpw(pass, hash1) == 0);
181 | after = clock();
182 | printf("First hash check with bcrypt_checkpw: %s\n", ret?"OK":"FAIL");
183 | printf("Time taken: %f seconds\n",
184 | (double)(after - before) / CLOCKS_PER_SEC);
185 |
186 | before = clock();
187 | ret = (bcrypt_checkpw(pass, hash2) == 0);
188 | after = clock();
189 | printf("Second hash check with bcrypt_checkpw: %s\n", ret?"OK":"FAIL");
190 | printf("Time taken: %f seconds\n",
191 | (double)(after - before) / CLOCKS_PER_SEC);
192 |
193 | return 0;
194 | }
195 | #endif
196 |
--------------------------------------------------------------------------------
/src/bcrypt/blowfish/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 2.8.12)
2 |
3 | add_library(blowfish crypt_blowfish.c crypt_gensalt.c wrapper.c)
4 |
--------------------------------------------------------------------------------
/src/bcrypt/blowfish/crypt_gensalt.c:
--------------------------------------------------------------------------------
1 | /*
2 | * Written by Solar Designer in 2000-2011.
3 | * No copyright is claimed, and the software is hereby placed in the public
4 | * domain. In case this attempt to disclaim copyright and place the software
5 | * in the public domain is deemed null and void, then the software is
6 | * Copyright (c) 2000-2011 Solar Designer and it is hereby released to the
7 | * general public under the following terms:
8 | *
9 | * Redistribution and use in source and binary forms, with or without
10 | * modification, are permitted.
11 | *
12 | * There's ABSOLUTELY NO WARRANTY, express or implied.
13 | *
14 | * See crypt_blowfish.c for more information.
15 | *
16 | * This file contains salt generation functions for the traditional and
17 | * other common crypt(3) algorithms, except for bcrypt which is defined
18 | * entirely in crypt_blowfish.c.
19 | */
20 |
21 | #include
22 |
23 | #include
24 | #ifndef __set_errno
25 | #define __set_errno(val) errno = (val)
26 | #endif
27 |
28 | /* Just to make sure the prototypes match the actual definitions */
29 | #include "crypt_gensalt.h"
30 |
31 | unsigned char _crypt_itoa64[64 + 1] =
32 | "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
33 |
34 | char *_crypt_gensalt_traditional_rn(const char *prefix, unsigned long count,
35 | const char *input, int size, char *output, int output_size)
36 | {
37 | (void) prefix;
38 |
39 | if (size < 2 || output_size < 2 + 1 || (count && count != 25)) {
40 | if (output_size > 0) output[0] = '\0';
41 | __set_errno((output_size < 2 + 1) ? ERANGE : EINVAL);
42 | return NULL;
43 | }
44 |
45 | output[0] = _crypt_itoa64[(unsigned int)input[0] & 0x3f];
46 | output[1] = _crypt_itoa64[(unsigned int)input[1] & 0x3f];
47 | output[2] = '\0';
48 |
49 | return output;
50 | }
51 |
52 | char *_crypt_gensalt_extended_rn(const char *prefix, unsigned long count,
53 | const char *input, int size, char *output, int output_size)
54 | {
55 | unsigned long value;
56 |
57 | (void) prefix;
58 |
59 | /* Even iteration counts make it easier to detect weak DES keys from a look
60 | * at the hash, so they should be avoided */
61 | if (size < 3 || output_size < 1 + 4 + 4 + 1 ||
62 | (count && (count > 0xffffff || !(count & 1)))) {
63 | if (output_size > 0) output[0] = '\0';
64 | __set_errno((output_size < 1 + 4 + 4 + 1) ? ERANGE : EINVAL);
65 | return NULL;
66 | }
67 |
68 | if (!count) count = 725;
69 |
70 | output[0] = '_';
71 | output[1] = _crypt_itoa64[count & 0x3f];
72 | output[2] = _crypt_itoa64[(count >> 6) & 0x3f];
73 | output[3] = _crypt_itoa64[(count >> 12) & 0x3f];
74 | output[4] = _crypt_itoa64[(count >> 18) & 0x3f];
75 | value = (unsigned long)(unsigned char)input[0] |
76 | ((unsigned long)(unsigned char)input[1] << 8) |
77 | ((unsigned long)(unsigned char)input[2] << 16);
78 | output[5] = _crypt_itoa64[value & 0x3f];
79 | output[6] = _crypt_itoa64[(value >> 6) & 0x3f];
80 | output[7] = _crypt_itoa64[(value >> 12) & 0x3f];
81 | output[8] = _crypt_itoa64[(value >> 18) & 0x3f];
82 | output[9] = '\0';
83 |
84 | return output;
85 | }
86 |
87 | char *_crypt_gensalt_md5_rn(const char *prefix, unsigned long count,
88 | const char *input, int size, char *output, int output_size)
89 | {
90 | unsigned long value;
91 |
92 | (void) prefix;
93 |
94 | if (size < 3 || output_size < 3 + 4 + 1 || (count && count != 1000)) {
95 | if (output_size > 0) output[0] = '\0';
96 | __set_errno((output_size < 3 + 4 + 1) ? ERANGE : EINVAL);
97 | return NULL;
98 | }
99 |
100 | output[0] = '$';
101 | output[1] = '1';
102 | output[2] = '$';
103 | value = (unsigned long)(unsigned char)input[0] |
104 | ((unsigned long)(unsigned char)input[1] << 8) |
105 | ((unsigned long)(unsigned char)input[2] << 16);
106 | output[3] = _crypt_itoa64[value & 0x3f];
107 | output[4] = _crypt_itoa64[(value >> 6) & 0x3f];
108 | output[5] = _crypt_itoa64[(value >> 12) & 0x3f];
109 | output[6] = _crypt_itoa64[(value >> 18) & 0x3f];
110 | output[7] = '\0';
111 |
112 | if (size >= 6 && output_size >= 3 + 4 + 4 + 1) {
113 | value = (unsigned long)(unsigned char)input[3] |
114 | ((unsigned long)(unsigned char)input[4] << 8) |
115 | ((unsigned long)(unsigned char)input[5] << 16);
116 | output[7] = _crypt_itoa64[value & 0x3f];
117 | output[8] = _crypt_itoa64[(value >> 6) & 0x3f];
118 | output[9] = _crypt_itoa64[(value >> 12) & 0x3f];
119 | output[10] = _crypt_itoa64[(value >> 18) & 0x3f];
120 | output[11] = '\0';
121 | }
122 |
123 | return output;
124 | }
125 |
--------------------------------------------------------------------------------
/src/bcrypt/blowfish/wrapper.c:
--------------------------------------------------------------------------------
1 | /*
2 | * Written by Solar Designer in 2000-2014.
3 | * No copyright is claimed, and the software is hereby placed in the public
4 | * domain. In case this attempt to disclaim copyright and place the software
5 | * in the public domain is deemed null and void, then the software is
6 | * Copyright (c) 2000-2014 Solar Designer and it is hereby released to the
7 | * general public under the following terms:
8 | *
9 | * Redistribution and use in source and binary forms, with or without
10 | * modification, are permitted.
11 | *
12 | * There's ABSOLUTELY NO WARRANTY, express or implied.
13 | *
14 | * See crypt_blowfish.c for more information.
15 | */
16 |
17 | #include
18 | #include
19 |
20 | #include
21 | #ifndef __set_errno
22 | #define __set_errno(val) errno = (val)
23 | #endif
24 |
25 | #ifdef TEST
26 | #include
27 | #include
28 | #include
29 | #include
30 | #include
31 | #include
32 | #ifdef TEST_THREADS
33 | #include
34 | #endif
35 | #endif
36 |
37 | #define CRYPT_OUTPUT_SIZE (7 + 22 + 31 + 1)
38 | #define CRYPT_GENSALT_OUTPUT_SIZE (7 + 22 + 1)
39 |
40 | #if defined(__GLIBC__) && defined(_LIBC)
41 | #define __SKIP_GNU
42 | #endif
43 | #include "ow-crypt.h"
44 |
45 | #include "crypt_blowfish.h"
46 | #include "crypt_gensalt.h"
47 |
48 | #if defined(__GLIBC__) && defined(_LIBC)
49 | /* crypt.h from glibc-crypt-2.1 will define struct crypt_data for us */
50 | #include "crypt.h"
51 | extern char *__md5_crypt_r(const char *key, const char *salt,
52 | char *buffer, int buflen);
53 | /* crypt-entry.c needs to be patched to define __des_crypt_r rather than
54 | * __crypt_r, and not define crypt_r and crypt at all */
55 | extern char *__des_crypt_r(const char *key, const char *salt,
56 | struct crypt_data *data);
57 | extern struct crypt_data _ufc_foobar;
58 | #endif
59 |
60 | static int _crypt_data_alloc(void **data, int *size, int need)
61 | {
62 | void *updated;
63 |
64 | if (*data && *size >= need) return 0;
65 |
66 | updated = realloc(*data, need);
67 |
68 | if (!updated) {
69 | #ifndef __GLIBC__
70 | /* realloc(3) on glibc sets errno, so we don't need to bother */
71 | __set_errno(ENOMEM);
72 | #endif
73 | return -1;
74 | }
75 |
76 | #if defined(__GLIBC__) && defined(_LIBC)
77 | if (need >= sizeof(struct crypt_data))
78 | ((struct crypt_data *)updated)->initialized = 0;
79 | #endif
80 |
81 | *data = updated;
82 | *size = need;
83 |
84 | return 0;
85 | }
86 |
87 | static char *_crypt_retval_magic(char *retval, const char *setting,
88 | char *output, int size)
89 | {
90 | if (retval)
91 | return retval;
92 |
93 | if (_crypt_output_magic(setting, output, size))
94 | return NULL; /* shouldn't happen */
95 |
96 | return output;
97 | }
98 |
99 | #if defined(__GLIBC__) && defined(_LIBC)
100 | /*
101 | * Applications may re-use the same instance of struct crypt_data without
102 | * resetting the initialized field in order to let crypt_r() skip some of
103 | * its initialization code. Thus, it is important that our multiple hashing
104 | * algorithms either don't conflict with each other in their use of the
105 | * data area or reset the initialized field themselves whenever required.
106 | * Currently, the hashing algorithms simply have no conflicts: the first
107 | * field of struct crypt_data is the 128-byte large DES key schedule which
108 | * __des_crypt_r() calculates each time it is called while the two other
109 | * hashing algorithms use less than 128 bytes of the data area.
110 | */
111 |
112 | char *__crypt_rn(__const char *key, __const char *setting,
113 | void *data, int size)
114 | {
115 | if (setting[0] == '$' && setting[1] == '2')
116 | return _crypt_blowfish_rn(key, setting, (char *)data, size);
117 | if (setting[0] == '$' && setting[1] == '1')
118 | return __md5_crypt_r(key, setting, (char *)data, size);
119 | if (setting[0] == '$' || setting[0] == '_') {
120 | __set_errno(EINVAL);
121 | return NULL;
122 | }
123 | if (size >= sizeof(struct crypt_data))
124 | return __des_crypt_r(key, setting, (struct crypt_data *)data);
125 | __set_errno(ERANGE);
126 | return NULL;
127 | }
128 |
129 | char *__crypt_ra(__const char *key, __const char *setting,
130 | void **data, int *size)
131 | {
132 | if (setting[0] == '$' && setting[1] == '2') {
133 | if (_crypt_data_alloc(data, size, CRYPT_OUTPUT_SIZE))
134 | return NULL;
135 | return _crypt_blowfish_rn(key, setting, (char *)*data, *size);
136 | }
137 | if (setting[0] == '$' && setting[1] == '1') {
138 | if (_crypt_data_alloc(data, size, CRYPT_OUTPUT_SIZE))
139 | return NULL;
140 | return __md5_crypt_r(key, setting, (char *)*data, *size);
141 | }
142 | if (setting[0] == '$' || setting[0] == '_') {
143 | __set_errno(EINVAL);
144 | return NULL;
145 | }
146 | if (_crypt_data_alloc(data, size, sizeof(struct crypt_data)))
147 | return NULL;
148 | return __des_crypt_r(key, setting, (struct crypt_data *)*data);
149 | }
150 |
151 | char *__crypt_r(__const char *key, __const char *setting,
152 | struct crypt_data *data)
153 | {
154 | return _crypt_retval_magic(
155 | __crypt_rn(key, setting, data, sizeof(*data)),
156 | setting, (char *)data, sizeof(*data));
157 | }
158 |
159 | char *__crypt(__const char *key, __const char *setting)
160 | {
161 | return _crypt_retval_magic(
162 | __crypt_rn(key, setting, &_ufc_foobar, sizeof(_ufc_foobar)),
163 | setting, (char *)&_ufc_foobar, sizeof(_ufc_foobar));
164 | }
165 | #else
166 | char *crypt_rn(const char *key, const char *setting, void *data, int size)
167 | {
168 | return _crypt_blowfish_rn(key, setting, (char *)data, size);
169 | }
170 |
171 | char *crypt_ra(const char *key, const char *setting,
172 | void **data, int *size)
173 | {
174 | if (_crypt_data_alloc(data, size, CRYPT_OUTPUT_SIZE))
175 | return NULL;
176 | return _crypt_blowfish_rn(key, setting, (char *)*data, *size);
177 | }
178 |
179 | char *crypt_r(const char *key, const char *setting, void *data)
180 | {
181 | return _crypt_retval_magic(
182 | crypt_rn(key, setting, data, CRYPT_OUTPUT_SIZE),
183 | setting, (char *)data, CRYPT_OUTPUT_SIZE);
184 | }
185 |
186 | char *crypt(const char *key, const char *setting)
187 | {
188 | static char output[CRYPT_OUTPUT_SIZE];
189 |
190 | return _crypt_retval_magic(
191 | crypt_rn(key, setting, output, sizeof(output)),
192 | setting, output, sizeof(output));
193 | }
194 |
195 | #define __crypt_gensalt_rn crypt_gensalt_rn
196 | #define __crypt_gensalt_ra crypt_gensalt_ra
197 | #define __crypt_gensalt crypt_gensalt
198 | #endif
199 |
200 | char *__crypt_gensalt_rn(const char *prefix, unsigned long count,
201 | const char *input, int size, char *output, int output_size)
202 | {
203 | char *(*use)(const char *_prefix, unsigned long _count,
204 | const char *_input, int _size,
205 | char *_output, int _output_size);
206 |
207 | /* This may be supported on some platforms in the future */
208 | if (!input) {
209 | __set_errno(EINVAL);
210 | return NULL;
211 | }
212 |
213 | if (!strncmp(prefix, "$2a$", 4) || !strncmp(prefix, "$2b$", 4) ||
214 | !strncmp(prefix, "$2y$", 4))
215 | use = _crypt_gensalt_blowfish_rn;
216 | else
217 | if (!strncmp(prefix, "$1$", 3))
218 | use = _crypt_gensalt_md5_rn;
219 | else
220 | if (prefix[0] == '_')
221 | use = _crypt_gensalt_extended_rn;
222 | else
223 | if (!prefix[0] ||
224 | (prefix[0] && prefix[1] &&
225 | memchr(_crypt_itoa64, prefix[0], 64) &&
226 | memchr(_crypt_itoa64, prefix[1], 64)))
227 | use = _crypt_gensalt_traditional_rn;
228 | else {
229 | __set_errno(EINVAL);
230 | return NULL;
231 | }
232 |
233 | return use(prefix, count, input, size, output, output_size);
234 | }
235 |
236 | char *__crypt_gensalt_ra(const char *prefix, unsigned long count,
237 | const char *input, int size)
238 | {
239 | char output[CRYPT_GENSALT_OUTPUT_SIZE];
240 | char *retval;
241 |
242 | retval = __crypt_gensalt_rn(prefix, count,
243 | input, size, output, sizeof(output));
244 |
245 | if (retval) {
246 | retval = strdup(retval);
247 | #ifndef __GLIBC__
248 | /* strdup(3) on glibc sets errno, so we don't need to bother */
249 | if (!retval)
250 | __set_errno(ENOMEM);
251 | #endif
252 | }
253 |
254 | return retval;
255 | }
256 |
257 | char *__crypt_gensalt(const char *prefix, unsigned long count,
258 | const char *input, int size)
259 | {
260 | static char output[CRYPT_GENSALT_OUTPUT_SIZE];
261 |
262 | return __crypt_gensalt_rn(prefix, count,
263 | input, size, output, sizeof(output));
264 | }
265 |
266 | #if defined(__GLIBC__) && defined(_LIBC)
267 | weak_alias(__crypt_rn, crypt_rn)
268 | weak_alias(__crypt_ra, crypt_ra)
269 | weak_alias(__crypt_r, crypt_r)
270 | weak_alias(__crypt, crypt)
271 | weak_alias(__crypt_gensalt_rn, crypt_gensalt_rn)
272 | weak_alias(__crypt_gensalt_ra, crypt_gensalt_ra)
273 | weak_alias(__crypt_gensalt, crypt_gensalt)
274 | weak_alias(crypt, fcrypt)
275 | #endif
276 |
277 | #ifdef TEST
278 | static const char *tests[][3] = {
279 | {"$2a$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW",
280 | "U*U"},
281 | {"$2a$05$CCCCCCCCCCCCCCCCCCCCC.VGOzA784oUp/Z0DY336zx7pLYAy0lwK",
282 | "U*U*"},
283 | {"$2a$05$XXXXXXXXXXXXXXXXXXXXXOAcXxm9kjPGEMsLznoKqmqw7tc8WCx4a",
284 | "U*U*U"},
285 | {"$2a$05$abcdefghijklmnopqrstuu5s2v8.iXieOjg/.AySBTTZIIVFJeBui",
286 | "0123456789abcdefghijklmnopqrstuvwxyz"
287 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
288 | "chars after 72 are ignored"},
289 | {"$2x$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e",
290 | "\xa3"},
291 | {"$2x$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e",
292 | "\xff\xff\xa3"},
293 | {"$2y$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e",
294 | "\xff\xff\xa3"},
295 | {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.nqd1wy.pTMdcvrRWxyiGL2eMz.2a85.",
296 | "\xff\xff\xa3"},
297 | {"$2b$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e",
298 | "\xff\xff\xa3"},
299 | {"$2y$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq",
300 | "\xa3"},
301 | {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq",
302 | "\xa3"},
303 | {"$2b$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq",
304 | "\xa3"},
305 | {"$2x$05$/OK.fbVrR/bpIqNJ5ianF.o./n25XVfn6oAPaUvHe.Csk4zRfsYPi",
306 | "1\xa3" "345"},
307 | {"$2x$05$/OK.fbVrR/bpIqNJ5ianF.o./n25XVfn6oAPaUvHe.Csk4zRfsYPi",
308 | "\xff\xa3" "345"},
309 | {"$2x$05$/OK.fbVrR/bpIqNJ5ianF.o./n25XVfn6oAPaUvHe.Csk4zRfsYPi",
310 | "\xff\xa3" "34" "\xff\xff\xff\xa3" "345"},
311 | {"$2y$05$/OK.fbVrR/bpIqNJ5ianF.o./n25XVfn6oAPaUvHe.Csk4zRfsYPi",
312 | "\xff\xa3" "34" "\xff\xff\xff\xa3" "345"},
313 | {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.ZC1JEJ8Z4gPfpe1JOr/oyPXTWl9EFd.",
314 | "\xff\xa3" "34" "\xff\xff\xff\xa3" "345"},
315 | {"$2y$05$/OK.fbVrR/bpIqNJ5ianF.nRht2l/HRhr6zmCp9vYUvvsqynflf9e",
316 | "\xff\xa3" "345"},
317 | {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.nRht2l/HRhr6zmCp9vYUvvsqynflf9e",
318 | "\xff\xa3" "345"},
319 | {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.6IflQkJytoRVc1yuaNtHfiuq.FRlSIS",
320 | "\xa3" "ab"},
321 | {"$2x$05$/OK.fbVrR/bpIqNJ5ianF.6IflQkJytoRVc1yuaNtHfiuq.FRlSIS",
322 | "\xa3" "ab"},
323 | {"$2y$05$/OK.fbVrR/bpIqNJ5ianF.6IflQkJytoRVc1yuaNtHfiuq.FRlSIS",
324 | "\xa3" "ab"},
325 | {"$2x$05$6bNw2HLQYeqHYyBfLMsv/OiwqTymGIGzFsA4hOTWebfehXHNprcAS",
326 | "\xd1\x91"},
327 | {"$2x$05$6bNw2HLQYeqHYyBfLMsv/O9LIGgn8OMzuDoHfof8AQimSGfcSWxnS",
328 | "\xd0\xc1\xd2\xcf\xcc\xd8"},
329 | {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.swQOIzjOiJ9GHEPuhEkvqrUyvWhEMx6",
330 | "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
331 | "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
332 | "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
333 | "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
334 | "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
335 | "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
336 | "chars after 72 are ignored as usual"},
337 | {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.R9xrDjiycxMbQE2bp.vgqlYpW5wx2yy",
338 | "\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55"
339 | "\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55"
340 | "\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55"
341 | "\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55"
342 | "\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55"
343 | "\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55"},
344 | {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.9tQZzcJfm3uj2NvJ/n5xkhpqLrMpWCe",
345 | "\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff"
346 | "\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff"
347 | "\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff"
348 | "\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff"
349 | "\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff"
350 | "\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff"},
351 | {"$2a$05$CCCCCCCCCCCCCCCCCCCCC.7uG0VCzI2bS7j6ymqJi9CdcdxiRTWNy",
352 | ""},
353 | {"*0", "", "$2a$03$CCCCCCCCCCCCCCCCCCCCC."},
354 | {"*0", "", "$2a$32$CCCCCCCCCCCCCCCCCCCCC."},
355 | {"*0", "", "$2c$05$CCCCCCCCCCCCCCCCCCCCC."},
356 | {"*0", "", "$2z$05$CCCCCCCCCCCCCCCCCCCCC."},
357 | {"*0", "", "$2`$05$CCCCCCCCCCCCCCCCCCCCC."},
358 | {"*0", "", "$2{$05$CCCCCCCCCCCCCCCCCCCCC."},
359 | {"*1", "", "*0"},
360 | {NULL}
361 | };
362 |
363 | #define which tests[0]
364 |
365 | static volatile sig_atomic_t running;
366 |
367 | static void handle_timer(int signum)
368 | {
369 | (void) signum;
370 | running = 0;
371 | }
372 |
373 | static void *run(void *arg)
374 | {
375 | unsigned long count = 0;
376 | int i = 0;
377 | void *data = NULL;
378 | int size = 0x12345678;
379 |
380 | do {
381 | const char *hash = tests[i][0];
382 | const char *key = tests[i][1];
383 | const char *setting = tests[i][2];
384 |
385 | if (!tests[++i][0])
386 | i = 0;
387 |
388 | if (setting && strlen(hash) < 30) /* not for benchmark */
389 | continue;
390 |
391 | if (strcmp(crypt_ra(key, hash, &data, &size), hash)) {
392 | printf("%d: FAILED (crypt_ra/%d/%lu)\n",
393 | (int)((char *)arg - (char *)0), i, count);
394 | free(data);
395 | return NULL;
396 | }
397 | count++;
398 | } while (running);
399 |
400 | free(data);
401 | return count + (char *)0;
402 | }
403 |
404 | int main(void)
405 | {
406 | struct itimerval it;
407 | struct tms buf;
408 | clock_t clk_tck, start_real, start_virtual, end_real, end_virtual;
409 | unsigned long count;
410 | void *data;
411 | int size;
412 | char *setting1, *setting2;
413 | int i;
414 | #ifdef TEST_THREADS
415 | pthread_t t[TEST_THREADS];
416 | void *t_retval;
417 | #endif
418 |
419 | data = NULL;
420 | size = 0x12345678;
421 |
422 | for (i = 0; tests[i][0]; i++) {
423 | const char *hash = tests[i][0];
424 | const char *key = tests[i][1];
425 | const char *setting = tests[i][2];
426 | const char *p;
427 | int ok = !setting || strlen(hash) >= 30;
428 | int o_size;
429 | char s_buf[30], o_buf[61];
430 | if (!setting) {
431 | memcpy(s_buf, hash, sizeof(s_buf) - 1);
432 | s_buf[sizeof(s_buf) - 1] = 0;
433 | setting = s_buf;
434 | }
435 |
436 | __set_errno(0);
437 | p = crypt(key, setting);
438 | if ((!ok && !errno) || strcmp(p, hash)) {
439 | printf("FAILED (crypt/%d)\n", i);
440 | return 1;
441 | }
442 |
443 | if (ok && strcmp(crypt(key, hash), hash)) {
444 | printf("FAILED (crypt/%d)\n", i);
445 | return 1;
446 | }
447 |
448 | for (o_size = -1; o_size <= (int)sizeof(o_buf); o_size++) {
449 | int ok_n = ok && o_size == (int)sizeof(o_buf);
450 | const char *x = "abc";
451 | strcpy(o_buf, x);
452 | if (o_size >= 3) {
453 | x = "*0";
454 | if (setting[0] == '*' && setting[1] == '0')
455 | x = "*1";
456 | }
457 | __set_errno(0);
458 | p = crypt_rn(key, setting, o_buf, o_size);
459 | if ((ok_n && (!p || strcmp(p, hash))) ||
460 | (!ok_n && (!errno || p || strcmp(o_buf, x)))) {
461 | printf("FAILED (crypt_rn/%d)\n", i);
462 | return 1;
463 | }
464 | }
465 |
466 | __set_errno(0);
467 | p = crypt_ra(key, setting, &data, &size);
468 | if ((ok && (!p || strcmp(p, hash))) ||
469 | (!ok && (!errno || p || strcmp((char *)data, hash)))) {
470 | printf("FAILED (crypt_ra/%d)\n", i);
471 | return 1;
472 | }
473 | }
474 |
475 | setting1 = crypt_gensalt(which[0], 12, data, size);
476 | if (!setting1 || strncmp(setting1, "$2a$12$", 7)) {
477 | puts("FAILED (crypt_gensalt)\n");
478 | return 1;
479 | }
480 |
481 | setting2 = crypt_gensalt_ra(setting1, 12, data, size);
482 | if (strcmp(setting1, setting2)) {
483 | puts("FAILED (crypt_gensalt_ra/1)\n");
484 | return 1;
485 | }
486 |
487 | (*(char *)data)++;
488 | setting1 = crypt_gensalt_ra(setting2, 12, data, size);
489 | if (!strcmp(setting1, setting2)) {
490 | puts("FAILED (crypt_gensalt_ra/2)\n");
491 | return 1;
492 | }
493 |
494 | free(setting1);
495 | free(setting2);
496 | free(data);
497 |
498 | #if defined(_SC_CLK_TCK) || !defined(CLK_TCK)
499 | clk_tck = sysconf(_SC_CLK_TCK);
500 | #else
501 | clk_tck = CLK_TCK;
502 | #endif
503 |
504 | running = 1;
505 | signal(SIGALRM, handle_timer);
506 |
507 | memset(&it, 0, sizeof(it));
508 | it.it_value.tv_sec = 5;
509 | setitimer(ITIMER_REAL, &it, NULL);
510 |
511 | start_real = times(&buf);
512 | start_virtual = buf.tms_utime + buf.tms_stime;
513 |
514 | count = (char *)run((char *)0) - (char *)0;
515 |
516 | end_real = times(&buf);
517 | end_virtual = buf.tms_utime + buf.tms_stime;
518 | if (end_virtual == start_virtual) end_virtual++;
519 |
520 | printf("%.1f c/s real, %.1f c/s virtual\n",
521 | (float)count * clk_tck / (end_real - start_real),
522 | (float)count * clk_tck / (end_virtual - start_virtual));
523 |
524 | #ifdef TEST_THREADS
525 | running = 1;
526 | it.it_value.tv_sec = 60;
527 | setitimer(ITIMER_REAL, &it, NULL);
528 | start_real = times(&buf);
529 |
530 | for (i = 0; i < TEST_THREADS; i++)
531 | if (pthread_create(&t[i], NULL, run, i + (char *)0)) {
532 | perror("pthread_create");
533 | return 1;
534 | }
535 |
536 | for (i = 0; i < TEST_THREADS; i++) {
537 | if (pthread_join(t[i], &t_retval)) {
538 | perror("pthread_join");
539 | continue;
540 | }
541 | if (!t_retval) continue;
542 | count = (char *)t_retval - (char *)0;
543 | end_real = times(&buf);
544 | printf("%d: %.1f c/s real\n", i,
545 | (float)count * clk_tck / (end_real - start_real));
546 | }
547 | #endif
548 |
549 | return 0;
550 | }
551 | #endif
552 |
--------------------------------------------------------------------------------
/src/bcrypt/hash.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include "bcrypt.h"
3 | #include "logger.h"
4 | #include "constant.h"
5 |
6 | char* bcrypt_hash(const char *data) {
7 | char salt[BCRYPT_HASHSIZE];
8 | log_debug("BCrypt data -> `%s`", data);
9 | if (bcrypt_gensalt(10, salt)) {
10 | log_fatal("BCrypt generate salt error");
11 | }
12 | log_debug("BCrypt salt -> `%s`", salt);
13 |
14 | char *hash = (char *)malloc(BCRYPT_HASHSIZE);
15 | if (bcrypt_hashpw(data, salt, hash)) {
16 | log_fatal("BCrypt generate hash error");
17 | }
18 | log_debug("BCrypt hash -> `%s`", hash);
19 | return hash;
20 | }
21 |
22 | int bcrypt_verify(const char *data, const char *hash) {
23 | if (bcrypt_checkpw(data, hash) == 0) {
24 | return TRUE;
25 | }
26 | return FALSE;
27 | }
28 |
--------------------------------------------------------------------------------
/src/cleardns.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include "loader.h"
7 | #include "logger.h"
8 | #include "system.h"
9 | #include "assets.h"
10 | #include "adguard.h"
11 | #include "crontab.h"
12 | #include "constant.h"
13 | #include "dnsproxy.h"
14 | #include "overture.h"
15 | #include "structure.h"
16 |
17 | struct {
18 | char *config;
19 | uint8_t debug;
20 | uint8_t verbose;
21 | } settings;
22 |
23 | void init(int argc, char *argv[]) { // return config file
24 | settings.config = strdup(CONFIG_FILE);
25 | settings.debug = FALSE;
26 | settings.verbose = FALSE;
27 |
28 | if (getenv("CONFIG") != NULL) {
29 | free(settings.config);
30 | settings.config = strdup(getenv("CONFIG"));
31 | }
32 | if (getenv("DEBUG") != NULL && !strcmp(getenv("DEBUG"), "TRUE")) {
33 | settings.debug = TRUE;
34 | }
35 | if (getenv("VERBOSE") != NULL && !strcmp(getenv("VERBOSE"), "TRUE")) {
36 | settings.verbose = TRUE;
37 | }
38 |
39 | for (int i = 0; i < argc; ++i) {
40 | if (!strcmp(argv[i], "--debug")) {
41 | settings.debug = TRUE;
42 | }
43 | if (!strcmp(argv[i], "--verbose")) {
44 | settings.verbose = TRUE;
45 | }
46 | if (!strcmp(argv[i], "--version")) {
47 | printf("ClearDNS version %s\n", VERSION); // show version
48 | exit(0);
49 | }
50 | if (!strcmp(argv[i], "--help")) {
51 | printf("\n%s\n", HELP_MSG); // show help message
52 | exit(0);
53 | }
54 | if (!strcmp(argv[i], "--config")) {
55 | if (i + 1 == argc) {
56 | log_error("Option `--config` missing value");
57 | exit(1);
58 | }
59 | free(settings.config);
60 | settings.config = strdup(argv[++i]); // use custom config file
61 | }
62 | }
63 | log_debug("Config file -> %s", settings.config);
64 | }
65 |
66 | void cleardns() { // cleardns service
67 | uint8_t rust_log_level = RUST_LOG_INFO; // using info level as default
68 | if (settings.verbose || settings.debug) {
69 | LOG_LEVEL = LOG_DEBUG; // enable debug log level
70 | rust_log_level = RUST_LOG_DEBUG;
71 | if (settings.debug) {
72 | rust_log_level = RUST_LOG_TRACE; // enable rust trace log
73 | }
74 | }
75 | assets_log_init(rust_log_level, LOG_PREFIX); // setting rust log level
76 |
77 | create_folder(EXPOSE_DIR);
78 | create_folder(WORK_DIR);
79 | chdir(EXPOSE_DIR);
80 | load_config(settings.config); // configure parser
81 | free(settings.config);
82 | if (settings.debug) { // debug mode enabled
83 | loader.diverter->debug = TRUE;
84 | loader.domestic->debug = TRUE;
85 | loader.foreign->debug = TRUE;
86 | if (loader.crond != NULL) {
87 | loader.crond->debug = TRUE;
88 | }
89 | if (loader.filter != NULL) {
90 | loader.filter->debug = TRUE;
91 | }
92 | }
93 |
94 | log_info("Start loading process");
95 | process_list_init();
96 | assets_load(loader.resource);
97 | process_list_append(dnsproxy_load("Domestic", loader.domestic, "domestic.json"));
98 | process_list_append(dnsproxy_load("Foreign", loader.foreign, "foreign.json"));
99 | process_list_append(overture_load(loader.diverter, "overture.json"));
100 | overture_free(loader.diverter);
101 | dnsproxy_free(loader.domestic);
102 | dnsproxy_free(loader.foreign);
103 | assets_free(loader.resource);
104 | if (loader.crond != NULL) {
105 | process_list_append(crontab_load(loader.crond)); // free crontab struct later
106 | }
107 | if (loader.filter != NULL) {
108 | process_list_append(adguard_load(loader.filter, ADGUARD_DIR));
109 | adguard_free(loader.filter);
110 | }
111 |
112 | for (char **script = loader.script; *script != NULL; ++script) { // running custom script
113 | log_info("Run custom script -> `%s`", *script);
114 | run_command(*script);
115 | }
116 | string_list_free(loader.script);
117 |
118 | process_list_run(); // start all process
119 | if (loader.crond != NULL) { // assets not disabled
120 | kill(getpid(), SIGALRM); // send alarm signal to cleardns
121 | crontab_free(loader.crond);
122 | }
123 | process_list_daemon(); // daemon all process
124 | }
125 |
126 | int main(int argc, char *argv[]) {
127 | init(argc, argv);
128 | log_info("ClearDNS server start (%s)", VERSION);
129 | cleardns();
130 | return 0;
131 | }
132 |
--------------------------------------------------------------------------------
/src/common/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 2.8.12)
2 |
3 | add_library(common json.c sundry.c system.c structure.c)
4 | target_link_libraries(common to_json)
5 |
--------------------------------------------------------------------------------
/src/common/json.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include "cJSON.h"
4 | #include "logger.h"
5 | #include "sundry.h"
6 | #include "to_json.h"
7 | #include "constant.h"
8 | #include "structure.h"
9 |
10 | uint8_t is_json_suffix(const char *file_name) { // whether file name end with `.json` suffix
11 | if (strlen(file_name) <= 5) { // file name shorter than `.json`
12 | return FALSE;
13 | }
14 | if (!strcmp(file_name + strlen(file_name) - 5, ".json")) { // xxx.json
15 | return TRUE;
16 | }
17 | return FALSE;
18 | }
19 |
20 | char* to_json_format(const char *content) { // convert JSON / TOML / YAML to json format (failed -> NULL)
21 | const char *json_string = to_json(content); // convert to json format
22 | if (json_string == NULL) {
23 | log_warn("JSON convert error ->\n%s", content);
24 | return NULL; // convert failed
25 | }
26 | char *json_content = strdup(json_string); // load string into owner heap
27 | free_rust_string(json_string); // free rust string
28 | log_debug("JSON convert result ->\n%s", json_content);
29 | return json_content;
30 | }
31 |
32 | cJSON* json_field_get(cJSON *entry, const char *key) { // fetch key from json map (create when key not exist)
33 | cJSON *sub = entry->child;
34 | while (sub != NULL) { // traverse all keys
35 | if (!strcmp(sub->string, key)) { // target key found
36 | return sub;
37 | }
38 | sub = sub->next;
39 | }
40 | cJSON *new = cJSON_CreateObject(); // create new json key
41 | cJSON_AddItemToObject(entry, key, new);
42 | return new;
43 | }
44 |
45 | void json_field_replace(cJSON *entry, const char *key, cJSON *content) {
46 | if (!cJSON_ReplaceItemInObject(entry, key, content)) { // key not exist
47 | cJSON_AddItemToObject(entry, key, content); // add new json key
48 | }
49 | }
50 |
51 | int json_int_value(char *caption, cJSON *json) { // json int or string value -> int
52 | if (cJSON_IsNumber(json)) {
53 | return json->valueint;
54 | } else if (cJSON_IsString(json)) {
55 | char *p;
56 | int int_ret = (int)strtol(json->valuestring, &p, 10);
57 | if (int_ret == 0 && strcmp(json->valuestring, "0") != 0) { // invalid number in string
58 | log_fatal("`%s` not a valid number", caption);
59 | }
60 | return int_ret;
61 | } else {
62 | log_fatal("`%s` must be number or string", caption);
63 | }
64 | return 0; // never reach
65 | }
66 |
67 | uint8_t json_bool_value(char *caption, cJSON *json) { // json bool value -> bool
68 | if (!cJSON_IsBool(json)) {
69 | log_fatal("`%s` must be boolean", caption);
70 | }
71 | return json->valueint;
72 | }
73 |
74 | char* json_string_value(char* caption, cJSON *json) { // json string value -> string
75 | if (!cJSON_IsString(json)) {
76 | log_fatal("`%s` must be string", caption);
77 | }
78 | return strdup(json->valuestring);
79 | }
80 |
81 | char** json_string_list_value(char *caption, cJSON *json, char **string_list) { // json string array
82 | if (cJSON_IsString(json)) {
83 | string_list_append(&string_list, json->valuestring);
84 | } else if (cJSON_IsArray(json)) {
85 | json = json->child;
86 | while (json != NULL) {
87 | if (!cJSON_IsString(json)) {
88 | log_fatal("`%s` must be string array", caption);
89 | }
90 | string_list_append(&string_list, json->valuestring);
91 | json = json->next; // next key
92 | }
93 | } else if (!cJSON_IsNull(json)) { // allow null -> empty string list
94 | log_fatal("`%s` must be array or string", caption);
95 | }
96 | return string_list;
97 | }
98 |
99 | uint32_t** json_uint32_list_value(char *caption, cJSON *json, uint32_t **uint32_list) { // json uint32 array
100 | if (cJSON_IsNumber(json)) {
101 | uint32_list_append(&uint32_list, json->valueint);
102 | } else if (cJSON_IsArray(json)) {
103 | json = json->child;
104 | while (json != NULL) {
105 | if (!cJSON_IsNumber(json)) {
106 | log_fatal("`%s` must be number array", caption);
107 | }
108 | uint32_list_append(&uint32_list, json->valueint);
109 | json = json->next; // next key
110 | }
111 | } else if (!cJSON_IsNull(json)) { // allow null -> empty uint32 list
112 | log_fatal("`%s` must be array or number", caption);
113 | }
114 | return uint32_list;
115 | }
116 |
--------------------------------------------------------------------------------
/src/common/structure.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include "structure.h"
6 |
7 | char** string_list_init() { // init string list
8 | char **string_list = (char **)malloc(sizeof(char *));
9 | *string_list = NULL; // list end sign
10 | return string_list;
11 | }
12 |
13 | uint32_t string_list_len(char **string_list) { // get len of string list
14 | uint32_t num = 0;
15 | while(string_list[num++] != NULL); // get list size
16 | return num - 1;
17 | }
18 |
19 | void string_list_append(char ***string_list, const char *string) {
20 | uint32_t len = string_list_len(*string_list);
21 | *string_list = (char **)realloc(*string_list, sizeof(char *) * (len + 2)); // extend string list
22 | (*string_list)[len] = strdup(string);
23 | (*string_list)[len + 1] = NULL; // list end sign
24 | }
25 |
26 | void string_list_update(char ***base_list, char **update_list) { // combine two string list
27 | for (char **string = update_list; *string != NULL; ++string) {
28 | string_list_append(base_list, *string);
29 | }
30 | }
31 |
32 | void string_list_free(char **string_list) { // free string list
33 | for (char **string = string_list; *string != NULL; ++string) {
34 | free(*string);
35 | }
36 | free(string_list);
37 | }
38 |
39 | char* string_list_dump(char **string_list) { // ['a', 'b', 'c', ...]
40 | if (string_list_len(string_list) == 0) {
41 | return strdup("[]"); // empty string list
42 | }
43 | char *string_ret = (char *)malloc(2);
44 | strcpy(string_ret, "[");
45 | for (char **string = string_list; *string != NULL; ++string) {
46 | string_ret = (char *)realloc(string_ret, strlen(string_ret) + strlen(*string) + 5);
47 | strcat(strcat(strcat(string_ret, "'"), *string), "', ");
48 | }
49 | string_ret[strlen(string_ret) - 2] = '\0';
50 | return strcat(string_ret, "]");
51 | }
52 |
53 | uint32_t** uint32_list_init() { // init uint32 list
54 | uint32_t **uint32_list = (uint32_t **)malloc(sizeof(uint32_t *));
55 | *uint32_list = NULL;
56 | return uint32_list;
57 | }
58 |
59 | uint32_t uint32_list_len(uint32_t **uint32_list) { // get len of uint32 list
60 | uint32_t num = 0;
61 | while(uint32_list[num++] != NULL); // get list size
62 | return num - 1;
63 | }
64 |
65 | void uint32_list_append(uint32_t ***uint32_list, uint32_t number) { // add new uint32 at the end of list
66 | uint32_t len = uint32_list_len(*uint32_list);
67 | *uint32_list = (uint32_t **)realloc(*uint32_list, sizeof(uint32_t *) * (len + 2));
68 | (*uint32_list)[len] = (uint32_t *)malloc(sizeof(uint32_t));
69 | *(*uint32_list)[len] = number;
70 | (*uint32_list)[len + 1] = NULL; // list end sign
71 | }
72 |
73 | void uint32_list_update(uint32_t ***base_list, uint32_t **update_list) { // combine two uint32 list
74 | for (uint32_t **number = update_list; *number != NULL; ++number) {
75 | uint32_list_append(base_list, **number);
76 | }
77 | }
78 |
79 | void uint32_list_free(uint32_t **uint32_list) { // free uint32 list
80 | for (uint32_t **number = uint32_list; *number != NULL; ++number) {
81 | free(*number);
82 | }
83 | free(uint32_list);
84 | }
85 |
86 | char* uint32_list_dump(uint32_t **uint32_list) { // [1, 2, 3, ...]
87 | if (uint32_list_len(uint32_list) == 0) {
88 | return strdup("[]"); // empty int list
89 | }
90 | char uint32_str[12];
91 | char *string_ret = (char *)malloc(2);
92 | strcpy(string_ret, "[");
93 | for (uint32_t **number = uint32_list; *number != NULL; ++number) {
94 | sprintf(uint32_str, "%u", **number);
95 | string_ret = (char *)realloc(string_ret, strlen(string_ret) + 15);
96 | string_ret = strcat(strcat(string_ret, uint32_str), ", ");
97 | }
98 | string_ret[strlen(string_ret) - 2] = '\0';
99 | return strcat(string_ret, "]");
100 | }
101 |
--------------------------------------------------------------------------------
/src/common/sundry.c:
--------------------------------------------------------------------------------
1 | #ifndef _GNU_SOURCE
2 | #define _GNU_SOURCE // NOLINT
3 | #endif
4 |
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include "logger.h"
10 | #include "constant.h"
11 | #include "structure.h"
12 |
13 | const char* show_bool(uint8_t value) { // return `true` or `false`
14 | if (value) {
15 | return "true";
16 | }
17 | return "false";
18 | }
19 |
20 | char* string_join(const char *base, const char *add) { // combine string
21 | char *ret = (char *)malloc(strlen(base) + strlen(add) + 1);
22 | return strcat(strcpy(ret, base), add);
23 | }
24 |
25 | char* string_load(const char *fmt, ...) {
26 | va_list ap;
27 | va_start(ap, fmt);
28 | char *buf = NULL;
29 | vasprintf(&buf, fmt, ap);
30 | return buf;
31 | }
32 |
33 | char* uint32_to_string(uint32_t number) { // convert uint32 -> string
34 | char to_str[11]; // MAX_LEN(uint32) -> 4294967296(10-bytes)
35 | sprintf(to_str, "%u", number);
36 | return strdup(to_str);
37 | }
38 |
39 | void string_list_debug(char *describe, char **string_list) { // show string list in debug log
40 | char *string_ret = string_list_dump(string_list);
41 | log_debug("%s -> %s", describe, string_ret);
42 | free(string_ret);
43 | }
44 |
45 | void uint32_list_debug(char *describe, uint32_t **uint32_list) { // show uint32 list in debug log
46 | char *string_ret = uint32_list_dump(uint32_list);
47 | log_debug("%s -> %s", describe, string_ret);
48 | free(string_ret);
49 | }
50 |
51 | uint8_t check_port(uint16_t port) { // whether port is valid
52 | if (port > 0) { // 1 ~ 65535 (uint16_t <= 65535)
53 | return TRUE;
54 | }
55 | return FALSE;
56 | }
57 |
--------------------------------------------------------------------------------
/src/common/system.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include "logger.h"
7 | #include "sundry.h"
8 | #include "system.h"
9 | #include "constant.h"
10 |
11 | int run_command(const char *command) { // running command under system shell
12 | log_debug("Run command -> `%s`", command);
13 | int ret_code = system(command) / 256;
14 | if (ret_code != 0) {
15 | log_warn("Command `%s` return non-zero code %d", command, ret_code);
16 | }
17 | return ret_code;
18 | }
19 |
20 | void create_folder(const char *folder) { // create folder
21 | if (!access(folder, 0)) { // target is file or folder
22 | struct stat buf;
23 | stat(folder, &buf);
24 | if (!(S_IFDIR & buf.st_mode)) { // target is not folder
25 | log_error("Create folder `%s` failed -> target is file", folder);
26 | } else {
27 | log_debug("Create folder `%s` skip -> folder exist", folder);
28 | }
29 | } else if (mkdir(folder, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)) { // folder -> 755
30 | log_perror("Create folder `%s` failed -> ", folder);
31 | } else {
32 | log_debug("Create folder `%s` success", folder);
33 | }
34 | }
35 |
36 | uint8_t is_file_exist(const char *file) { // whether file exist
37 | if (!access(file, 0)) { // target is file or folder
38 | struct stat buf;
39 | stat(file, &buf);
40 | if (S_IFREG & buf.st_mode) { // target is file
41 | return TRUE;
42 | }
43 | }
44 | return FALSE;
45 | }
46 |
47 | void save_file(const char *file, const char *content) { // save content into file
48 | log_debug("Write into `%s` ->\n%s", file, content);
49 | FILE* fp = fopen(file , "w");
50 | if (fp == NULL) {
51 | log_fatal("Fail to open file -> %s", file);
52 | }
53 | fputs(content, fp);
54 | fclose(fp);
55 | log_debug("Save `%s` success", file);
56 | }
57 |
58 | void file_append(const char *base_file, const char *append_file) { // append_file >> base_file
59 | char *append_cmd = string_load("cat %s >> %s", append_file, base_file);
60 | run_command(append_cmd);
61 | free(append_cmd);
62 | }
63 |
64 | char* read_file(const char *file) { // read file content
65 | log_debug("Read file -> %s", file);
66 | FILE *fp = fopen(file, "rb");
67 | if (fp == NULL) { // file open failed
68 | log_fatal("File `%s` open failed", file);
69 | }
70 | fseek(fp, 0, SEEK_END);
71 | long length = ftell(fp); // get file length
72 | char *content = (char*)malloc(length + 1); // malloc new memory
73 | if (content == NULL) {
74 | log_fatal("No enough memory for reading file"); // file too large
75 | }
76 | rewind(fp);
77 | fread(content, 1, length, fp); // read file stream
78 | content[length] = '\0'; // set end flag
79 | fclose(fp);
80 | log_debug("File `%s` read success ->\n%s", file, content);
81 | return content;
82 | }
83 |
84 | void save_string_list(const char *file, char **string_list) { // save string list into file
85 | log_debug("Write string list into `%s`", file);
86 | FILE* fp = fopen(file , "w");
87 | if (fp == NULL) {
88 | log_fatal("Fail to open file -> %s", file);
89 | }
90 | for (char **string = string_list; *string != NULL; ++string) {
91 | log_debug("File append -> `%s`", *string);
92 | fputs(*string, fp);
93 | fputs("\n", fp);
94 | }
95 | fclose(fp);
96 | log_debug("Save `%s` success", file);
97 | }
98 |
--------------------------------------------------------------------------------
/src/loader/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 2.8.12)
2 |
3 | add_library(loader config.c default.c loader.c parser.c)
4 | target_link_libraries(loader applet common)
5 |
--------------------------------------------------------------------------------
/src/loader/config.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include "config.h"
4 | #include "logger.h"
5 | #include "sundry.h"
6 | #include "constant.h"
7 | #include "structure.h"
8 |
9 | cleardns_config* config_init() { // init config struct of cleardns
10 | cleardns_config *config = (cleardns_config *)malloc(sizeof(cleardns_config));
11 | config->port = DNS_PORT;
12 | config->cache.size = 0; // disable cache in default
13 | config->cache.enable = FALSE;
14 | config->cache.optimistic = FALSE;
15 |
16 | config->domestic.port = DOMESTIC_PORT;
17 | config->domestic.ipv6 = TRUE;
18 | config->domestic.verify = TRUE;
19 | config->domestic.parallel = TRUE;
20 | config->domestic.bootstrap = string_list_init();
21 | config->domestic.fallback = string_list_init();
22 | config->domestic.primary = string_list_init();
23 |
24 | config->foreign.port = FOREIGN_PORT;
25 | config->foreign.ipv6 = TRUE;
26 | config->foreign.verify = TRUE;
27 | config->foreign.parallel = TRUE;
28 | config->foreign.bootstrap = string_list_init();
29 | config->foreign.fallback = string_list_init();
30 | config->foreign.primary = string_list_init();
31 |
32 | config->diverter.port = DIVERTER_PORT;
33 | config->diverter.gfwlist = string_list_init();
34 | config->diverter.china_ip = string_list_init();
35 | config->diverter.chinalist = string_list_init();
36 |
37 | config->adguard.port = ADGUARD_PORT;
38 | config->adguard.enable = TRUE;
39 | config->adguard.username = strdup(ADGUARD_USER);
40 | config->adguard.password = strdup(ADGUARD_PASSWD);
41 |
42 | config->assets.disable = FALSE;
43 | config->assets.cron = strdup(UPDATE_CRON);
44 | config->assets.resources = assets_init();
45 |
46 | config->reject = uint32_list_init();
47 | config->hosts = string_list_init();
48 | config->ttl = string_list_init();
49 | config->script = string_list_init();
50 | return config;
51 | }
52 |
53 | void config_dump(cleardns_config *config) { // dump config info of cleardns
54 | log_debug("DNS port -> %u", config->port);
55 | log_debug("Cache size -> %u", config->cache.size);
56 | log_debug("Cache enable -> %s", show_bool(config->cache.enable));
57 | log_debug("Cache optimistic -> %s", show_bool(config->cache.optimistic));
58 |
59 | log_debug("Domestic port -> %u", config->domestic.port);
60 | log_debug("Domestic ipv6 -> %s", show_bool(config->domestic.ipv6));
61 | log_debug("Domestic verify -> %s", show_bool(config->domestic.verify));
62 | log_debug("Domestic parallel -> %s", show_bool(config->domestic.parallel));
63 | string_list_debug("Domestic bootstrap", config->domestic.bootstrap);
64 | string_list_debug("Domestic fallback", config->domestic.fallback);
65 | string_list_debug("Domestic primary", config->domestic.primary);
66 |
67 | log_debug("Foreign port -> %u", config->foreign.port);
68 | log_debug("Foreign ipv6 -> %s", show_bool(config->foreign.ipv6));
69 | log_debug("Foreign verify -> %s", show_bool(config->foreign.verify));
70 | log_debug("Foreign parallel -> %s", show_bool(config->foreign.parallel));
71 | string_list_debug("Foreign bootstrap", config->foreign.bootstrap);
72 | string_list_debug("Foreign fallback", config->foreign.fallback);
73 | string_list_debug("Foreign primary", config->foreign.primary);
74 |
75 | log_debug("Diverter port -> %u", config->diverter.port);
76 | string_list_debug("Diverter gfwlist", config->diverter.gfwlist);
77 | string_list_debug("Diverter china-ip", config->diverter.china_ip);
78 | string_list_debug("Diverter chinalist", config->diverter.chinalist);
79 |
80 | log_debug("AdGuardHome port -> %u", config->adguard.port);
81 | log_debug("AdGuardHome enable -> %s", show_bool(config->adguard.enable));
82 | log_debug("AdGuardHome username -> %s", config->adguard.username);
83 | log_debug("AdGuardHome password -> %s", config->adguard.password);
84 |
85 | log_debug("Assets disable -> %s", show_bool(config->assets.disable));
86 | log_debug("Assets update cron -> `%s`", config->assets.cron);
87 | log_debug("Assets with %d resource items", assets_size(config->assets.resources));
88 | assets_dump(config->assets.resources);
89 |
90 | uint32_list_debug("DNS reject type", config->reject);
91 | string_list_debug("Domain TTL", config->ttl);
92 | string_list_debug("Hosts", config->hosts);
93 | string_list_debug("Custom script", config->script);
94 | }
95 |
96 | void config_free(cleardns_config *config) { // free config struct of cleardns
97 | string_list_free(config->domestic.bootstrap);
98 | string_list_free(config->domestic.fallback);
99 | string_list_free(config->domestic.primary);
100 |
101 | string_list_free(config->foreign.bootstrap);
102 | string_list_free(config->foreign.fallback);
103 | string_list_free(config->foreign.primary);
104 |
105 | string_list_free(config->diverter.gfwlist);
106 | string_list_free(config->diverter.china_ip);
107 | string_list_free(config->diverter.chinalist);
108 |
109 | free(config->adguard.username);
110 | free(config->adguard.password);
111 |
112 | free(config->assets.cron);
113 | assets_free(config->assets.resources);
114 |
115 | uint32_list_free(config->reject);
116 | string_list_free(config->hosts);
117 | string_list_free(config->ttl);
118 | string_list_free(config->script);
119 | free(config);
120 | }
121 |
--------------------------------------------------------------------------------
/src/loader/default.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include "json.h"
4 | #include "logger.h"
5 | #include "system.h"
6 |
7 | #define DEFAULT_CONFIG "\
8 | port: 53\n\
9 | \n\
10 | cache:\n\
11 | enable: true\n\
12 | size: 4194304\n\
13 | optimistic: true\n\
14 | \n\
15 | diverter:\n\
16 | port: 5353\n\
17 | \n\
18 | adguard:\n\
19 | enable: true\n\
20 | port: 80\n\
21 | username: admin\n\
22 | password: cleardns\n\
23 | \n\
24 | domestic:\n\
25 | port: 4053\n\
26 | bootstrap: 223.5.5.5\n\
27 | primary:\n\
28 | - tls://dns.alidns.com\n\
29 | - https://doh.pub/dns-query\n\
30 | fallback:\n\
31 | - 223.6.6.6\n\
32 | - 119.29.29.29\n\
33 | \n\
34 | foreign:\n\
35 | port: 6053\n\
36 | bootstrap: 8.8.8.8\n\
37 | primary:\n\
38 | - tls://dns.google\n\
39 | - https://dns.cloudflare.com/dns-query\n\
40 | fallback:\n\
41 | - 1.1.1.1\n\
42 | - 8.8.4.4\n\
43 | \n\
44 | assets:\n\
45 | cron: \"0 4 * * *\"\n\
46 | update:\n\
47 | gfwlist.txt: https://cdn.dnomd343.top/cleardns/gfwlist.txt\n\
48 | china-ip.txt: https://cdn.dnomd343.top/cleardns/china-ip.txt\n\
49 | chinalist.txt: https://cdn.dnomd343.top/cleardns/chinalist.txt\n\
50 | "
51 |
52 | void load_default_config(const char *config_file) {
53 | if (is_file_exist(config_file)) {
54 | log_debug("Configure file exist -> skip loading default");
55 | return;
56 | }
57 | log_info("Loading default configure file");
58 | char *config_content = NULL;
59 | if (is_json_suffix(config_file)) { // convert to json format
60 | config_content = to_json_format(DEFAULT_CONFIG);
61 | } else {
62 | config_content = strdup(DEFAULT_CONFIG);
63 | }
64 | save_file(config_file, config_content);
65 | free(config_content);
66 | }
67 |
--------------------------------------------------------------------------------
/src/loader/loader.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include "config.h"
4 | #include "loader.h"
5 | #include "logger.h"
6 | #include "parser.h"
7 | #include "sundry.h"
8 | #include "system.h"
9 | #include "default.h"
10 | #include "dnsproxy.h"
11 | #include "constant.h"
12 | #include "structure.h"
13 |
14 | struct cleardns loader;
15 |
16 | void load_diverter_asset(const char *file) {
17 | char *src_file = string_join(ASSETS_DIR, file);
18 | char *dst_file = string_join(WORK_DIR, file);
19 | file_append(dst_file, src_file);
20 | free(dst_file);
21 | free(src_file);
22 | }
23 |
24 | void load_diverter_assets() {
25 | load_diverter_asset(ASSET_CHINA_IP);
26 | load_diverter_asset(ASSET_GFW_LIST);
27 | load_diverter_asset(ASSET_CHINA_LIST);
28 | }
29 |
30 | dnsproxy* load_domestic(cleardns_config *config) {
31 | dnsproxy *domestic = dnsproxy_init(config->domestic.port);
32 | domestic->ipv6 = config->domestic.ipv6;
33 | domestic->verify = config->domestic.verify;
34 | domestic->parallel = config->domestic.parallel;
35 | if (config->cache.enable) {
36 | domestic->cache = config->cache.size;
37 | domestic->optimistic = config->cache.optimistic;
38 | }
39 | string_list_update(&domestic->bootstrap, config->domestic.bootstrap);
40 | string_list_update(&domestic->fallback, config->domestic.fallback);
41 | string_list_update(&domestic->primary, config->domestic.primary);
42 | return domestic;
43 | }
44 |
45 | dnsproxy* load_foreign(cleardns_config *config) {
46 | dnsproxy *foreign = dnsproxy_init(config->foreign.port);
47 | foreign->ipv6 = config->foreign.ipv6;
48 | foreign->verify = config->foreign.verify;
49 | foreign->parallel = config->foreign.parallel;
50 | if (config->cache.enable) {
51 | foreign->cache = config->cache.size;
52 | foreign->optimistic = config->cache.optimistic;
53 | }
54 | string_list_update(&foreign->bootstrap, config->foreign.bootstrap);
55 | string_list_update(&foreign->fallback, config->foreign.fallback);
56 | string_list_update(&foreign->primary, config->foreign.primary);
57 | return foreign;
58 | }
59 |
60 | overture* load_diverter(cleardns_config *config) {
61 | overture *diverter = overture_init();
62 | diverter->port = config->diverter.port;
63 | diverter->foreign_port = config->foreign.port;
64 | diverter->domestic_port = config->domestic.port;
65 |
66 | if (string_list_len(config->ttl)) {
67 | free(diverter->ttl_file);
68 | diverter->ttl_file = strdup(ASSET_TTL);
69 | char *ttl_file = string_join(WORK_DIR, ASSET_TTL);
70 | save_string_list(ttl_file, config->ttl);
71 | free(ttl_file);
72 | }
73 | if (string_list_len(config->hosts)) {
74 | free(diverter->host_file);
75 | diverter->host_file = strdup(ASSET_HOSTS);
76 | char *hosts_file = string_join(WORK_DIR, ASSET_HOSTS);
77 | save_string_list(hosts_file, config->hosts);
78 | free(hosts_file);
79 | }
80 |
81 | free(diverter->domestic_ip_file);
82 | free(diverter->foreign_domain_file);
83 | free(diverter->domestic_domain_file);
84 | diverter->domestic_ip_file = strdup(ASSET_CHINA_IP);
85 | diverter->foreign_domain_file = strdup(ASSET_GFW_LIST);
86 | diverter->domestic_domain_file = strdup(ASSET_CHINA_LIST);
87 |
88 | char *gfwlist = string_join(WORK_DIR, ASSET_GFW_LIST);
89 | char *china_ip = string_join(WORK_DIR, ASSET_CHINA_IP);
90 | char *chinalist = string_join(WORK_DIR, ASSET_CHINA_LIST);
91 | save_string_list(gfwlist, config->diverter.gfwlist);
92 | save_string_list(china_ip, config->diverter.china_ip);
93 | save_string_list(chinalist, config->diverter.chinalist);
94 | free(chinalist);
95 | free(china_ip);
96 | free(gfwlist);
97 |
98 | custom_gfwlist = config->diverter.gfwlist;
99 | custom_china_ip = config->diverter.china_ip;
100 | custom_chinalist = config->diverter.chinalist;
101 | config->diverter.gfwlist = string_list_init();
102 | config->diverter.china_ip = string_list_init();
103 | config->diverter.chinalist = string_list_init();
104 |
105 | uint32_list_update(&diverter->reject_type, config->reject);
106 | if (!config->assets.disable) {
107 | assets_extract(); // extract built-in resource
108 | load_diverter_assets();
109 | }
110 | return diverter;
111 | }
112 |
113 | adguard* load_filter(cleardns_config *config) {
114 | if (!config->adguard.enable) {
115 | return NULL; // disable adguard
116 | }
117 | adguard *filter = adguard_init();
118 | filter->dns_port = config->port;
119 | filter->web_port = config->adguard.port;
120 | filter->username = strdup(config->adguard.username);
121 | filter->password = strdup(config->adguard.password);
122 | char *diverter_port = uint32_to_string(config->diverter.port);
123 | filter->upstream = string_join("127.0.0.1:", diverter_port);
124 | free(diverter_port);
125 | return filter;
126 | }
127 |
128 | crontab* load_crond(cleardns_config *config) {
129 | if (config->assets.disable) {
130 | return NULL; // disable crond
131 | }
132 | crontab *crond = crontab_init();
133 | crond->cron = strdup(config->assets.cron);
134 | return crond;
135 | }
136 |
137 | asset** load_assets(cleardns_config *config) {
138 | asset **resources = assets_init();
139 | for (asset **res = config->assets.resources; *res != NULL; ++res) {
140 | assets_append(&resources, *res); // pointer movement
141 | }
142 | *(config->assets.resources) = NULL; // disable old assets list
143 | return resources;
144 | }
145 |
146 | void load_config(const char *config_file) { // parser and load cleardns configure
147 | cleardns_config *config = config_init();
148 | load_default_config(config_file); // load default configure
149 | config_parser(config, config_file); // configure parser
150 | config_dump(config);
151 |
152 | log_info("Loading configure options");
153 | if (!config->adguard.enable) {
154 | config->diverter.port = config->port; // override diverter port by dns port
155 | log_warn("Diverter port change -> %u", config->diverter.port);
156 | }
157 |
158 | loader.domestic = load_domestic(config);
159 | log_debug("Domestic options parser success");
160 |
161 | loader.foreign = load_foreign(config);
162 | log_debug("Foreign options parser success");
163 |
164 | loader.diverter = load_diverter(config);
165 | log_debug("Diverter options parser success");
166 |
167 | loader.filter = load_filter(config);
168 | log_debug("Filter options parser success");
169 |
170 | loader.crond = load_crond(config);
171 | log_debug("Crond options parser success");
172 |
173 | loader.resource = load_assets(config);
174 | log_debug("Assets options parser success");
175 |
176 | loader.script = string_list_init();
177 | string_list_update(&loader.script, config->script);
178 | log_debug("Custom script parser success");
179 |
180 | config_free(config);
181 | }
182 |
--------------------------------------------------------------------------------
/src/loader/parser.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include "json.h"
4 | #include "config.h"
5 | #include "logger.h"
6 | #include "sundry.h"
7 | #include "system.h"
8 |
9 | void cache_parser(cache_config *config, cJSON *json) { // cache options parser
10 | if (!cJSON_IsObject(json)) {
11 | log_fatal("`cache` must be object");
12 | }
13 | json = json->child;
14 | while (json != NULL) {
15 | if (!strcmp(json->string, "size")) {
16 | config->size = json_int_value("cache.size", json);
17 | }
18 | if (!strcmp(json->string, "enable")) {
19 | config->enable = json_bool_value("cache.enable", json);
20 | }
21 | if (!strcmp(json->string, "optimistic")) {
22 | config->optimistic = json_bool_value("cache.optimistic", json);
23 | }
24 | json = json->next; // next field
25 | }
26 | }
27 |
28 | void upstream_parser(char *caption, upstream_config *config, cJSON *json) { // upstream options parser
29 | if (!cJSON_IsObject(json)) {
30 | log_fatal("`%s` must be object", caption);
31 | }
32 | char *key_name;
33 | json = json->child;
34 | while (json != NULL) {
35 | if (!strcmp(json->string, "port")) {
36 | key_name = string_join(caption, ".port");
37 | config->port = json_int_value(key_name, json);
38 | free(key_name);
39 | }
40 | if (!strcmp(json->string, "ipv6")) {
41 | key_name = string_join(caption, ".ipv6");
42 | config->ipv6 = json_bool_value(key_name, json);
43 | free(key_name);
44 | }
45 | if (!strcmp(json->string, "verify")) {
46 | key_name = string_join(caption, ".verify");
47 | config->verify = json_bool_value(key_name, json);
48 | free(key_name);
49 | }
50 | if (!strcmp(json->string, "parallel")) {
51 | key_name = string_join(caption, ".parallel");
52 | config->parallel = json_bool_value(key_name, json);
53 | free(key_name);
54 | }
55 | if (!strcmp(json->string, "bootstrap")) {
56 | key_name = string_join(caption, ".bootstrap");
57 | config->bootstrap = json_string_list_value(key_name, json, config->bootstrap);
58 | free(key_name);
59 | }
60 | if (!strcmp(json->string, "fallback")) {
61 | key_name = string_join(caption, ".fallback");
62 | config->fallback = json_string_list_value(key_name, json, config->fallback);
63 | free(key_name);
64 | }
65 | if (!strcmp(json->string, "primary")) {
66 | key_name = string_join(caption, ".primary");
67 | config->primary = json_string_list_value(key_name, json, config->primary);
68 | free(key_name);
69 | }
70 | json = json->next; // next field
71 | }
72 | }
73 |
74 | void diverter_parser(diverter_config *config, cJSON *json) { // diverter options parser
75 | if (!cJSON_IsObject(json)) {
76 | log_fatal("`diverter` must be object");
77 | }
78 | json = json->child;
79 | while (json != NULL) {
80 | if (!strcmp(json->string, "port")) {
81 | config->port = json_int_value("diverter.port", json);
82 | }
83 | if (!strcmp(json->string, "gfwlist")) {
84 | config->gfwlist = json_string_list_value("diverter.gfwlist", json, config->gfwlist);
85 | }
86 | if (!strcmp(json->string, "china-ip")) {
87 | config->china_ip = json_string_list_value("diverter.china-ip", json, config->china_ip);
88 | }
89 | if (!strcmp(json->string, "chinalist")) {
90 | config->chinalist = json_string_list_value("diverter.chinalist", json, config->chinalist);
91 | }
92 | json = json->next; // next field
93 | }
94 | }
95 |
96 | void adguard_parser(adguard_config *config, cJSON *json) { // adguard options parser
97 | if (!cJSON_IsObject(json)) {
98 | log_fatal("`adguard` must be array");
99 | }
100 | json = json->child;
101 | while (json != NULL) {
102 | if (!strcmp(json->string, "port")) {
103 | config->port = json_int_value("adguard.port", json);
104 | }
105 | if (!strcmp(json->string, "enable")) {
106 | config->enable = json_bool_value("adguard.enable", json);
107 | }
108 | if (!strcmp(json->string, "username")) {
109 | free(config->username);
110 | config->username = json_string_value("adguard.username", json);
111 | }
112 | if (!strcmp(json->string, "password")) {
113 | free(config->password);
114 | config->password = json_string_value("adguard.password", json);
115 | }
116 | json = json->next; // next field
117 | }
118 | }
119 |
120 | void assets_parser(assets_config *config, cJSON *json) { // assets options parser
121 | if (!cJSON_IsObject(json)) {
122 | log_fatal("`assets` must be array");
123 | }
124 | json = json->child;
125 | while (json != NULL) {
126 | if (!strcmp(json->string, "disable")) {
127 | config->disable = json_bool_value("assets.disable", json);
128 | }
129 | if (!strcmp(json->string, "cron")) {
130 | config->cron = json_string_value("assets.cron", json);
131 | }
132 | if (!strcmp(json->string, "update")) {
133 | if (!cJSON_IsObject(json)) {
134 | log_fatal("`%s` must be map", "assets.update");
135 | }
136 | cJSON *asset_item = json->child;
137 | while (asset_item != NULL) { // traverse all json field
138 | asset *res = asset_init(asset_item->string);
139 | char *caption = string_join("assets.update.", asset_item->string);
140 | res->sources = json_string_list_value(caption, asset_item, res->sources);
141 | free(caption);
142 | assets_append(&config->resources, res);
143 | asset_item = asset_item->next;
144 | }
145 | }
146 | json = json->next; // next field
147 | }
148 | }
149 |
150 | void cleardns_parser(cleardns_config *config, const char *config_content) { // JSON format configure
151 | cJSON *json = cJSON_Parse(config_content);
152 | if (json == NULL) {
153 | log_fatal("ClearDNS configure format error");
154 | }
155 | json = json->child;
156 | while (json != NULL) {
157 | if (!strcmp(json->string, "port")) {
158 | config->port = json_int_value("port", json);
159 | }
160 | if (!strcmp(json->string, "cache")) {
161 | cache_parser(&config->cache, json);
162 | }
163 | if (!strcmp(json->string, "domestic")) {
164 | upstream_parser("domestic", &config->domestic, json);
165 | }
166 | if (!strcmp(json->string, "foreign")) {
167 | upstream_parser("foreign", &config->foreign, json);
168 | }
169 | if (!strcmp(json->string, "diverter")) {
170 | diverter_parser(&config->diverter, json);
171 | }
172 | if (!strcmp(json->string, "adguard")) {
173 | adguard_parser(&config->adguard, json);
174 | }
175 | if (!strcmp(json->string, "assets")) {
176 | assets_parser(&config->assets, json);
177 | }
178 | if (!strcmp(json->string, "reject")) {
179 | config->reject = json_uint32_list_value("reject", json, config->reject);
180 | }
181 | if (!strcmp(json->string, "hosts")) {
182 | config->hosts = json_string_list_value("hosts", json, config->hosts);
183 | }
184 | if (!strcmp(json->string, "ttl")) {
185 | config->ttl = json_string_list_value("ttl", json, config->ttl);
186 | }
187 | if (!strcmp(json->string, "custom")) {
188 | config->script = json_string_list_value("custom", json, config->script);
189 | }
190 | json = json->next; // next field
191 | }
192 | cJSON_free(json); // free JSON struct
193 | }
194 |
195 | void config_parser(cleardns_config *config, const char *config_file) {
196 | char *config_content = read_file(config_file);
197 |
198 | if (is_json_suffix(config_file)) { // JSON format
199 | log_info("Start JSON configure parser");
200 | } else { // YAML or TOML format
201 | log_info("Start configure parser");
202 | char *convert_ret = to_json_format(config_content);
203 | if (convert_ret == NULL) { // convert failed
204 | log_fatal("Configure parser error");
205 | }
206 | free(config_content);
207 | config_content = convert_ret;
208 | }
209 |
210 | cleardns_parser(config, config_content); // configure parser
211 | free(config_content);
212 | log_info("Configure parser success");
213 | }
214 |
--------------------------------------------------------------------------------
/src/to-json/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "to_json"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [lib]
7 | crate-type = ["staticlib"]
8 |
9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
10 |
11 | [dependencies]
12 | serde_json = "1.0.140"
13 | serde_yaml = "0.9.34"
14 | toml = "0.8.20"
15 |
--------------------------------------------------------------------------------
/src/to-json/cbindgen.toml:
--------------------------------------------------------------------------------
1 | language = "C"
2 | pragma_once = true
3 | include_version = false
4 |
--------------------------------------------------------------------------------
/src/to-json/src/ffi.rs:
--------------------------------------------------------------------------------
1 | use std::os::raw::c_char;
2 | use std::ffi::{CStr, CString};
3 | use crate::parser::{parser, Value};
4 |
5 | /// Load c-style string from `const char *` pointer.
6 | #[inline]
7 | unsafe fn load_c_string(ptr: *const c_char) -> String {
8 | CString::from(CStr::from_ptr(ptr))
9 | .into_string()
10 | .unwrap()
11 | }
12 |
13 | /// Export c-style string as `const char *` pointer.
14 | /// # NOTE
15 | /// The exported string cannot be freed by the c language `free(void *)` function,
16 | /// but should use the `free_rust_string` callback function, if this interface is
17 | /// not called, a memory leak will occur.
18 | #[inline]
19 | fn export_c_string(string: String) -> *const c_char {
20 | CString::new(string).unwrap().into_raw()
21 | }
22 |
23 | /// Free the exported c-style string.
24 | #[no_mangle]
25 | pub unsafe extern "C" fn free_rust_string(ptr: *const c_char) {
26 | let _ = CString::from_raw(ptr as *mut _); // reclaim rust ownership
27 | }
28 |
29 | /// Deserialize text content and serialize to JSON format.
30 | fn json_format(content: &str) -> Option {
31 | let result = match parser(&content) {
32 | Ok(value) => match value {
33 | Value::JSON(json) => serde_json::to_string(&json),
34 | Value::YAML(yaml) => serde_json::to_string(&yaml),
35 | Value::TOML(toml) => serde_json::to_string(&toml),
36 | },
37 | _ => return None,
38 | };
39 | match result {
40 | Ok(data) => Some(data),
41 | Err(_) => None,
42 | }
43 | }
44 |
45 | /// Format the input text into JSON format and return a c-style string, or return
46 | /// `NULL` if an error occurs.
47 | #[no_mangle]
48 | pub unsafe extern "C" fn to_json(content: *const c_char) -> *const c_char {
49 | let content = load_c_string(content);
50 | match json_format(&content) {
51 | Some(data) => export_c_string(data),
52 | None => std::ptr::null(),
53 | }
54 | }
55 |
56 | #[cfg(test)]
57 | mod tests {
58 | use super::json_format;
59 |
60 | const JSON_TEST_STR: &str = r#"
61 | {
62 | "int": 123,
63 | "bool": true,
64 | "float": 3.141592,
65 | "string": "json test",
66 | "array": [1, 2, 3, 4, 5],
67 | "object": {
68 | "sub": "test"
69 | }
70 | }
71 | "#;
72 |
73 | const YAML_TEST_STR: &str = r#"
74 | int: 123
75 | bool: true
76 | float: 3.141592
77 | string: "json test"
78 | array:
79 | - 1
80 | - 2
81 | - 3
82 | - 4
83 | - 5
84 | object:
85 | sub: test
86 | "#;
87 |
88 | const TOML_TEST_STR: &str = r#"
89 | int = 123
90 | bool = true
91 | float = 3.141592
92 | string = "json test"
93 | array = [ 1, 2, 3, 4, 5 ]
94 |
95 | [object]
96 | sub = "test"
97 | "#;
98 |
99 | #[inline]
100 | fn format(raw: &str) -> String {
101 | match json_format(raw) {
102 | Some(data) => data,
103 | None => panic!("format error"),
104 | }
105 | }
106 |
107 | #[test]
108 | fn json_input() {
109 | assert_eq!(
110 | format(JSON_TEST_STR),
111 | format(&json_format(JSON_TEST_STR).unwrap()),
112 | );
113 | }
114 |
115 | #[test]
116 | fn yaml_input() {
117 | assert_eq!(
118 | format(JSON_TEST_STR),
119 | format(&json_format(YAML_TEST_STR).unwrap()),
120 | );
121 | }
122 |
123 | #[test]
124 | fn toml_input() {
125 | assert_eq!(
126 | format(JSON_TEST_STR),
127 | format(&json_format(TOML_TEST_STR).unwrap()),
128 | );
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/src/to-json/src/lib.rs:
--------------------------------------------------------------------------------
1 | mod ffi;
2 | mod parser;
3 |
--------------------------------------------------------------------------------
/src/to-json/src/parser.rs:
--------------------------------------------------------------------------------
1 | use serde_json as json;
2 | use serde_yaml as yaml;
3 |
4 | #[derive(Debug)]
5 | pub enum Value {
6 | JSON(json::Value),
7 | YAML(yaml::Value),
8 | TOML(toml::Value),
9 | }
10 |
11 | /// Deserialize text content into JSON format.
12 | fn json_parser(content: &str) -> Option {
13 | match json::from_str::(content) {
14 | Ok(result) => Some(result),
15 | Err(_) => None,
16 | }
17 | }
18 |
19 | /// Deserialize text content into YAML format.
20 | fn yaml_parser(content: &str) -> Option {
21 | match yaml::from_str::(content) {
22 | Ok(result) => Some(result),
23 | Err(_) => None,
24 | }
25 | }
26 |
27 | /// Deserialize text content into TOML format.
28 | fn toml_parser(content: &str) -> Option {
29 | match toml::from_str::(content) {
30 | Ok(result) => Some(result),
31 | Err(_) => None,
32 | }
33 | }
34 |
35 | /// Try to deserialize the text in JSON, TOML and YAML format.
36 | pub fn parser(content: &str) -> Result {
37 | match json_parser(content) { // try JSON format
38 | Some(data) => Ok(Value::JSON(data)),
39 | None => match toml_parser(content) { // try TOML format
40 | Some(data) => Ok(Value::TOML(data)),
41 | None => match yaml_parser(content) { // try YAML format
42 | Some(data) => Ok(Value::YAML(data)),
43 | None => Err("unknown input format"),
44 | }
45 | }
46 | }
47 | }
48 |
49 | #[cfg(test)]
50 | mod tests {
51 | use super::Value;
52 | use super::parser;
53 | use super::json_parser;
54 | use super::yaml_parser;
55 | use super::toml_parser;
56 |
57 | const JSON_STR: &str = "{\"test\": \"ok\"}";
58 | const YAML_STR: &str = "test: ok";
59 | const TOML_STR: &str = "test = \"ok\"";
60 |
61 | #[test]
62 | fn json() {
63 | assert!(json_parser("").is_none()); // parse invalid text
64 | assert!(json_parser(JSON_STR).is_some());
65 | }
66 |
67 | #[test]
68 | fn yaml() {
69 | assert!(yaml_parser("&").is_none()); // parse invalid text
70 | assert!(yaml_parser(YAML_STR).is_some());
71 | }
72 |
73 | #[test]
74 | fn toml() {
75 | assert!(toml_parser(".").is_none()); // parse invalid text
76 | assert!(toml_parser(TOML_STR).is_some());
77 | }
78 |
79 | #[test]
80 | fn global() {
81 | match parser(JSON_STR).unwrap() {
82 | Value::JSON(_) => (),
83 | _ => panic!("JSON parser error"),
84 | };
85 | match parser(YAML_STR).unwrap() {
86 | Value::YAML(_) => (),
87 | _ => panic!("YAML parser error"),
88 | };
89 | match parser(TOML_STR).unwrap() {
90 | Value::TOML(_) => (),
91 | _ => panic!("TOML parser error"),
92 | };
93 | assert!(parser("\0").is_err()); // parse invalid text
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/utils/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 2.8.12)
2 |
3 | add_library(utils cJSON.c assets.c logger.c process.c)
4 | target_link_libraries(utils assets)
5 |
--------------------------------------------------------------------------------
/src/utils/assets.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include "assets.h"
5 | #include "loader.h"
6 | #include "logger.h"
7 | #include "sundry.h"
8 | #include "system.h"
9 | #include "constant.h"
10 | #include "structure.h"
11 |
12 | asset **update_info;
13 |
14 | char **custom_gfwlist;
15 | char **custom_china_ip;
16 | char **custom_chinalist;
17 |
18 | void assets_update_entry();
19 | void extract(const char *file);
20 |
21 | asset* asset_init(const char *name) { // init asset item
22 | asset *res = (asset *)malloc(sizeof(asset));
23 | res->file = strdup(name);
24 | res->sources = string_list_init(); // with multi sources
25 | return res;
26 | }
27 |
28 | asset** assets_init() { // init assets list
29 | asset **asset_list = (asset **)malloc(sizeof(asset *));
30 | *asset_list = NULL; // list end sign
31 | return asset_list;
32 | }
33 |
34 | void assets_free(asset **asset_list) { // free assets list
35 | for (asset **res = asset_list; *res != NULL; ++res) {
36 | string_list_free((*res)->sources);
37 | free((*res)->file);
38 | free(*res);
39 | }
40 | free(asset_list);
41 | }
42 |
43 | uint32_t assets_size(asset **asset_list) { // get size of assets list
44 | uint32_t num = 0;
45 | while(asset_list[num++] != NULL); // get list size
46 | return num - 1;
47 | }
48 |
49 | void assets_dump(asset **asset_list) { // dump assets list content into debug log
50 | for (asset **res = asset_list; *res != NULL; ++res) { // iterate over each item
51 | char *sources = string_list_dump((*res)->sources);
52 | log_debug("Asset item `%s` -> %s", (*res)->file, sources);
53 | free(sources);
54 | }
55 | }
56 |
57 | void assets_append(asset ***asset_list, asset *res) { // push asset item for assets list
58 | uint32_t len = assets_size(*asset_list);
59 | *asset_list = (asset **)realloc(*asset_list, sizeof(asset *) * (len + 2)); // extend asset list
60 | (*asset_list)[len] = res;
61 | (*asset_list)[len + 1] = NULL; // list end sign
62 | }
63 |
64 | void assets_load(asset **info) { // load assets list
65 | update_info = assets_init();
66 | for (asset **res = info; *res != NULL; ++res) {
67 | assets_append(&update_info, *res); // pointer movement
68 | }
69 | *info = NULL; // disable old assets list
70 | assets_dump(update_info);
71 | log_info("Remote assets load success");
72 | signal(SIGALRM, assets_update_entry); // receive SIGALRM signal
73 | }
74 |
75 | void assets_update_entry() { // receive SIGALRM for update all assets
76 | if (assets_size(update_info) == 0) { // empty assets list
77 | log_info("Skip update assets");
78 | return;
79 | }
80 | log_info("Start updating assets");
81 | for (asset **res = update_info; *res != NULL; ++res) {
82 | char *content = string_list_dump((*res)->sources);
83 | log_debug("Updating `%s` -> %s", (*res)->file, content);
84 | if (asset_update((*res)->file, (*res)->sources, ASSETS_DIR)) {
85 | log_debug("Asset `%s` update success", (*res)->file);
86 | } else {
87 | log_warn("Asset `%s` update failed", (*res)->file);
88 | }
89 | free(content);
90 | }
91 |
92 | char *gfwlist = string_join(WORK_DIR, ASSET_GFW_LIST);
93 | char *china_ip = string_join(WORK_DIR, ASSET_CHINA_IP);
94 | char *chinalist = string_join(WORK_DIR, ASSET_CHINA_LIST);
95 | save_string_list(gfwlist, custom_gfwlist);
96 | save_string_list(china_ip, custom_china_ip);
97 | save_string_list(chinalist, custom_chinalist);
98 | free(chinalist);
99 | free(china_ip);
100 | free(gfwlist);
101 | load_diverter_assets(); // load assets data into `WORK_DIR`
102 |
103 | log_info("Restart overture to apply new assets");
104 | run_command("pgrep overture | xargs kill"); // restart overture
105 | log_info("Assets update complete");
106 | }
107 |
108 | void assets_extract() { // init assets and load update process
109 | log_info("Start loading assets");
110 | create_folder(ASSETS_DIR);
111 | extract(ASSET_GFW_LIST);
112 | extract(ASSET_CHINA_IP);
113 | extract(ASSET_CHINA_LIST);
114 | log_info("Assets loading complete");
115 | }
116 |
117 | void extract(const char *file) { // extract one asset file from `.tar.xz` file
118 | log_debug("Start extract `%s`", file);
119 | char *output_file = string_join(ASSETS_DIR, file);
120 | if (is_file_exist(output_file)) {
121 | log_debug("Assets `%s` exist -> skip extract", file);
122 | free(output_file);
123 | return;
124 | }
125 | free(output_file);
126 |
127 | char *extract_cmd = string_load("tar xf %s %s -C %s", ASSETS_PKG, file, ASSETS_DIR);
128 | if (run_command(extract_cmd)) {
129 | log_warn("Extract asset `%s` failed", file);
130 | } else {
131 | log_info("Extract asset `%s` success", file);
132 | }
133 | free(extract_cmd);
134 | }
135 |
--------------------------------------------------------------------------------
/src/utils/logger.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include "logger.h"
7 |
8 | int LOG_LEVEL = LOG_INFO; // default log level
9 |
10 | static const char *log_string[] = {
11 | "[DEBUG]",
12 | "[INFO]",
13 | "[WARN]",
14 | "[ERROR]",
15 | "[FATAL]",
16 | };
17 |
18 | static const char *log_color[] = {
19 | "\x1b[39m", // debug
20 | "\x1b[32m", // info
21 | "\x1b[33m", // warn
22 | "\x1b[31m", // error
23 | "\x1b[95m", // fatal
24 | };
25 |
26 | void fprint_prefix() { // print log prefix and time info
27 | time_t _time;
28 | time(&_time);
29 | struct timeval tv;
30 | gettimeofday(&tv, NULL);
31 | struct tm *t = localtime(&_time);
32 | fprintf(stderr, "\x1b[36m[%s]\x1b[0m", LOG_PREFIX);
33 | fprintf(stderr, " \x1b[90m%04d-%02d-%02d", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday);
34 | fprintf(stderr, " %02d:%02d:%02d.%03ld\x1b[0m", t->tm_hour, t->tm_min, t->tm_sec, tv.tv_usec / 1000);
35 | }
36 |
37 | void log_printf(int level, const char *fmt, ...) {
38 | if (level < LOG_LEVEL) { // skip low log level
39 | return;
40 | }
41 | va_list ap;
42 | va_start(ap, fmt);
43 | fprint_prefix();
44 | fprintf(stderr, " %s%s\x1b[0m ", log_color[level], log_string[level]); // show log level
45 | vfprintf(stderr, fmt, ap); // output log content
46 | fprintf(stderr, "\n"); // add LF after line
47 | fflush(stderr);
48 | va_end(ap);
49 | if (level == LOG_FATAL) {
50 | exit(1);
51 | }
52 | }
53 |
54 | void log_perror(const char *fmt, ...) {
55 | va_list ap;
56 | va_start(ap, fmt);
57 | fprint_prefix();
58 | fprintf(stderr, " %s%s\x1b[0m ", log_color[LOG_ERROR], log_string[LOG_ERROR]);
59 | vfprintf(stderr, fmt, ap); // output log content
60 | fflush(stderr);
61 | perror("");
62 | }
63 |
--------------------------------------------------------------------------------
/src/utils/process.c:
--------------------------------------------------------------------------------
1 | #ifndef _GNU_SOURCE
2 | #define _GNU_SOURCE // NOLINT
3 | #endif
4 |
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include "logger.h"
12 | #include "sundry.h"
13 | #include "process.h"
14 | #include "constant.h"
15 | #include "structure.h"
16 |
17 | process **process_list;
18 |
19 | uint8_t EXITED = FALSE;
20 | uint8_t EXITING = FALSE;
21 |
22 | void get_sub_exit();
23 | void get_exit_signal();
24 | char* get_exit_msg(int status);
25 | void server_exit(int exit_code);
26 | void process_dump(process *proc);
27 | void process_exec(process *proc);
28 |
29 | process* process_init(const char *caption, const char *bin) { // init process struct
30 | process *proc = (process *)malloc(sizeof(process));
31 | proc->pid = 0; // process not running
32 | proc->name = strdup(caption); // process caption
33 | proc->cmd = string_list_init();
34 | string_list_append(&proc->cmd, bin); // argv[0] normally be process file name
35 | proc->env = string_list_init(); // empty environment variable
36 | proc->cwd = WORK_DIR; // current working directory
37 | return proc;
38 | }
39 |
40 | void process_dump(process *proc) { // output process options into debug log
41 | char *process_cmd = string_list_dump(proc->cmd);
42 | char *process_env = string_list_dump(proc->env);
43 | log_debug("%s env variable -> %s", proc->name, process_env);
44 | log_debug("%s cwd -> %s", proc->name, proc->cwd);
45 | log_debug("%s command -> %s", proc->name, process_cmd);
46 | free(process_env);
47 | free(process_cmd);
48 | }
49 |
50 | void process_exec(process *proc) {
51 | pid_t pid;
52 | log_info("%s start", proc->name);
53 | process_dump(proc);
54 | if ((pid = fork()) < 0) { // fork error
55 | log_perror("%s fork error -> ", proc->name);
56 | server_exit(EXIT_FORK_ERROR);
57 | } else if (pid == 0) { // child process
58 | if (chdir(proc->cwd)) { // change working directory
59 | log_perror("%s with invalid cwd `%s` -> ", proc->name, proc->cwd);
60 | exit(EXIT_EXEC_ERROR);
61 | }
62 | pid_t sid = setsid(); // create new session -> detach current terminal
63 | if (sid == -1) { // session create failed
64 | log_warn("Subprocess failed to create new session");
65 | } else {
66 | log_debug("Subprocess at new session -> SID = %d", sid);
67 | }
68 | prctl(PR_SET_PDEATHSIG, SIGKILL); // child process die with father process
69 | if (execvpe(*(proc->cmd), proc->cmd, proc->env) < 0) {
70 | log_perror("%s exec error -> ", proc->name);
71 | exit(EXIT_EXEC_ERROR);
72 | }
73 | }
74 | proc->pid = pid;
75 | usleep(8000); // wait 8ms prevent log clutter
76 | log_info("%s running success -> PID = %d", proc->name, proc->pid);
77 | }
78 |
79 | void process_add_arg(process *proc, const char *arg) { // add argument for process
80 | string_list_append(&proc->cmd, arg);
81 | }
82 |
83 | void process_list_init() { // init process list
84 | process_list = (process **)malloc(sizeof(process *));
85 | *process_list = NULL;
86 | }
87 |
88 | void process_list_append(process *proc) { // add new process into process list
89 | int proc_num = 0;
90 | for (process **p = process_list; *p != NULL; ++p, ++proc_num); // get process number
91 | process_list = (process **)realloc(process_list, sizeof(process *) * (proc_num + 2));
92 | process_list[proc_num++] = proc; // add into process list
93 | process_list[proc_num] = NULL;
94 | log_debug("%s process added", proc->name);
95 | }
96 |
97 | void process_list_run() { // start process list
98 | signal(SIGINT, get_exit_signal); // catch Ctrl + C (2)
99 | signal(SIGQUIT, get_exit_signal); // catch Ctrl + \ (3)
100 | signal(SIGTERM, get_exit_signal); // catch exit signal (15)
101 | signal(SIGCHLD, get_sub_exit); // callback when child process die
102 | for (process **proc = process_list; *proc != NULL; ++proc) {
103 | process_exec(*proc);
104 | }
105 | log_info("Process start complete");
106 | }
107 |
108 | void process_list_daemon() {
109 | while (!EXITED) {
110 | pause();
111 | }
112 | }
113 |
114 | char* get_exit_msg(int status) { // get why the child process death
115 | if (WIFEXITED(status)) { // exit normally (with an exit-code)
116 | char *exit_code = uint32_to_string(WEXITSTATUS(status));
117 | char *exit_msg = string_join("Exit code ", exit_code);
118 | free(exit_code);
119 | return exit_msg;
120 | }
121 | if (WIFSIGNALED(status)) { // abnormal exit (with a signal)
122 | char *exit_sig = uint32_to_string(WTERMSIG(status));
123 | char *exit_msg = string_join("Killed by signal ", exit_sig);
124 | free(exit_sig);
125 | return exit_msg;
126 | }
127 | return strdup("Unknown reason");
128 | }
129 |
130 | void server_exit(int exit_code) { // kill sub process and exit
131 | while (EXITING) { // only run once
132 | pause();
133 | }
134 | EXITING = TRUE;
135 | log_info("ClearDNS start exit process");
136 | for (process **proc = process_list; *proc != NULL; ++proc) { // send SIGTERM to all subprocess
137 | log_info("%s send kill signal -> PID = %d", (*proc)->name, (*proc)->pid);
138 | kill((*proc)->pid, SIGTERM);
139 | }
140 | for (process **proc = process_list; *proc != NULL; ++proc) { // ensure all subprocess exited
141 | int status;
142 | log_debug("%s start blocking wait", (*proc)->name);
143 | int ret = waitpid((*proc)->pid, &status, 0); // blocking wait
144 | log_info("%s exit -> PID = %d", (*proc)->name, ret);
145 | }
146 | EXITED = TRUE;
147 | log_warn("ClearDNS exit");
148 | exit(exit_code);
149 | }
150 |
151 | void get_exit_signal() { // get SIGINT or SIGTERM signal
152 | log_info("Get exit signal");
153 | server_exit(EXIT_NORMAL); // normally exit
154 | }
155 |
156 | void get_sub_exit() { // catch child process exit
157 | if (EXITING) {
158 | log_debug("Skip handle SIGCHLD");
159 | return;
160 | }
161 | int status;
162 | log_debug("Start handle SIGCHLD");
163 | for (process **proc = process_list; *proc != NULL; ++proc) {
164 | if ((*proc)->pid == 0) {
165 | continue; // skip not running process
166 | }
167 | int wait_ret = waitpid((*proc)->pid, &status, WNOHANG); // non-blocking wait
168 | if (wait_ret == -1) { // process wait error
169 | log_perror("%s waitpid error -> ", (*proc)->name);
170 | server_exit(EXIT_WAIT_ERROR);
171 | } else if (wait_ret) { // catch process exit
172 | char *exit_msg = get_exit_msg(status);
173 | log_warn("%s (PID = %d) -> %s", (*proc)->name, (*proc)->pid, exit_msg);
174 | free(exit_msg);
175 | sleep(RESTART_DELAY); // reduce restart frequency
176 | process_exec(*proc);
177 | log_info("%s restart complete", (*proc)->name);
178 | return; // skip following check
179 | }
180 | }
181 | int wait_ret = waitpid(-1, &status, WNOHANG); // waitpid for all sub-process (non-blocking)
182 | if (wait_ret == -1) {
183 | log_perror("Waitpid error -> ");
184 | server_exit(EXIT_WAIT_ERROR);
185 | } else if (wait_ret) { // process exit
186 | char *exit_msg = get_exit_msg(status);
187 | log_debug("Subprocess (PID = %d) -> %s", wait_ret, exit_msg);
188 | free(exit_msg);
189 | }
190 | log_debug("Handle SIGCHLD complete");
191 | }
192 |
--------------------------------------------------------------------------------