├── .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 | --------------------------------------------------------------------------------