blob: 3b4ad8c60e0fe5ed7ee9e7483f21dff5f39554cc [file] [log] [blame]
b.liue9582032025-04-17 19:18:16 +08001#include <iostream>
2#include <numeric>
3
4#include "nginx-ssl-util.hpp"
5#include "nginx-util.hpp"
6
7static auto constexpr file_comment_auto_created =
8 std::string_view{"# This file is re-created when Nginx starts.\n"};
9
10// TODO(pst) replace it with blobmsg_get_string if upstream takes const:
11#ifndef NO_UBUS
12static inline auto _pst_get_string(const blob_attr* attr) -> char*
13{
14 return static_cast<char*>(blobmsg_data(attr));
15}
16#endif
17
18void create_lan_listen() // create empty files for compatibility:
19{
20 // TODO(pst): replace by dummies after transitioning nginx config to UCI:
21 std::vector<std::string> ips;
22
23#ifndef NO_UBUS
24 try {
25 auto loopback_status = ubus::call("network.interface.loopback", "status");
26
27 for (const auto* ip : loopback_status.filter("ipv4-address", "", "address")) {
28 ips.emplace_back(_pst_get_string(ip));
29 }
30
31 for (const auto* ip : loopback_status.filter("ipv6-address", "", "address")) {
32 ips.emplace_back(std::string{"["} + _pst_get_string(ip) + "]");
33 }
34 }
35 catch (const std::runtime_error&) { /* do nothing about it */
36 }
37
38 try {
39 auto lan_status = ubus::call("network.interface.lan", "status");
40
41 for (const auto* ip : lan_status.filter("ipv4-address", "", "address")) {
42 ips.emplace_back(_pst_get_string(ip));
43 }
44
45 for (const auto* ip : lan_status.filter("ipv6-address", "", "address")) {
46 ips.emplace_back(std::string{"["} + _pst_get_string(ip) + "]");
47 }
48
49 for (const auto* ip :
50 lan_status.filter("ipv6-prefix-assignment", "", "local-address", "address")) {
51 ips.emplace_back(std::string{"["} + _pst_get_string(ip) + "]");
52 }
53 }
54 catch (const std::runtime_error&) { /* do nothing about it */
55 }
56#else
57 ips.emplace_back("127.0.0.1");
58#endif
59
60 std::string listen = std::string{file_comment_auto_created};
61 std::string listen_default = std::string{file_comment_auto_created};
62 for (const auto& ip : ips) {
63 listen += "\tlisten " + ip + ":80;\n";
64 listen_default += "\tlisten " + ip + ":80 default_server;\n";
65 }
66 write_file(LAN_LISTEN, listen);
67 write_file(LAN_LISTEN_DEFAULT, listen_default);
68
69 std::string ssl_listen = std::string{file_comment_auto_created};
70 std::string ssl_listen_default = std::string{file_comment_auto_created};
71 for (const auto& ip : ips) {
72 ssl_listen += "\tlisten " + ip + ":443 ssl;\n";
73 ssl_listen_default += "\tlisten " + ip + ":443 ssl default_server;\n";
74 }
75 write_file(LAN_SSL_LISTEN, ssl_listen);
76 write_file(LAN_SSL_LISTEN_DEFAULT, ssl_listen_default);
77}
78
79inline auto change_if_starts_with(const std::string_view& subject,
80 const std::string_view& prefix,
81 const std::string_view& substitute,
82 const std::string_view& seperator = " \t\n;") -> std::string
83{
84 auto view = subject;
85 view = view.substr(view.find_first_not_of(seperator));
86 if (view.rfind(prefix, 0) == 0) {
87 if (view.size() == prefix.size()) {
88 return std::string{substitute};
89 }
90 view = view.substr(prefix.size());
91 if (seperator.find(view[0]) != std::string::npos) {
92 auto ret = std::string{substitute};
93 ret += view;
94 return ret;
95 }
96 }
97 return std::string{subject};
98}
99
100inline auto create_server_conf(const uci::section& sec, const std::string& indent = "")
101 -> std::string
102{
103 auto secname = sec.name();
104
105 auto legacypath = std::string{CONF_DIR} + secname + ".conf";
106 if (access(legacypath.c_str(), R_OK) == 0) {
107 auto message = std::string{"skipped UCI server 'nginx."} + secname;
108 message += "' as it could conflict with: " + legacypath + "\n";
109
110 // TODO(pst) std::cerr<<"create_server_conf notice: "<<message;
111
112 return indent + "# " + message;
113 } // else:
114
115 auto conf = indent + "server { #see uci show 'nginx." + secname + "'\n";
116
117 for (auto opt : sec) {
118 for (auto itm : opt) {
119 if (opt.name().rfind("uci_", 0) == 0) {
120 continue;
121 }
122 // else: standard opt.name()
123
124 auto val = itm.name();
125
126 if (opt.name() == "error_log") {
127 val = change_if_starts_with(val, "logd", "/proc/self/fd/1");
128 }
129
130 else if (opt.name() == "access_log") {
131 val = change_if_starts_with(val, "logd", "stderr");
132 }
133
134 conf += indent + "\t" + opt.name() + " " + itm.name() + ";\n";
135 }
136 }
137
138 conf += indent + "}\n";
139
140 return conf;
141}
142
143void init_uci(const uci::package& pkg)
144{
145 auto conf = std::string{file_comment_auto_created};
146
147 static const auto uci_http_config = std::string_view{"#UCI_HTTP_CONFIG\n"};
148
149 const auto tmpl = read_file(std::string{UCI_CONF} + ".template");
150 auto pos = tmpl.find(uci_http_config);
151
152 if (pos == std::string::npos) {
153 conf += tmpl;
154 }
155
156 else {
157 const auto index = tmpl.find_last_not_of(" \t", pos - 1);
158
159 const auto before = tmpl.begin() + index + 1;
160 const auto middle = tmpl.begin() + pos;
161 const auto after = middle + uci_http_config.length();
162
163 conf.append(tmpl.begin(), before);
164
165 const auto indent = std::string{before, middle};
166 for (auto sec : pkg) {
167 if (sec.type() == std::string_view{"server"}) {
168 conf += create_server_conf(sec, indent) + "\n";
169 }
170 }
171
172 conf.append(after, tmpl.end());
173 }
174
175 write_file(VAR_UCI_CONF, conf);
176}
177
178auto is_enabled(const uci::package& pkg) -> bool
179{
180 for (auto sec : pkg) {
181 if (sec.type() != std::string_view{"main"}) {
182 continue;
183 }
184 if (sec.name() != std::string_view{"global"}) {
185 continue;
186 }
187 for (auto opt : sec) {
188 if (opt.name() != "uci_enable") {
189 continue;
190 }
191 for (auto itm : opt) {
192 if (itm) {
193 return true;
194 }
195 }
196 }
197 }
198 return false;
199}
200
201/*
202 * ___________main_thread________________|______________thread_1________________
203 * create_lan_listen() or do nothing | config = uci::package("nginx")
204 * if config_enabled (set in thread_1): | config_enabled = is_enabled(config)
205 * then init_uci(config) | check_ssl(config, config_enabled)
206 */
207void init_lan()
208{
209 std::exception_ptr ex;
210 std::unique_ptr<uci::package> config;
211 bool config_enabled = false;
212 std::mutex configuring;
213
214 configuring.lock();
215 auto thrd = std::thread([&config, &config_enabled, &configuring, &ex] {
216 try {
217 config = std::make_unique<uci::package>("nginx");
218 config_enabled = is_enabled(*config);
219 configuring.unlock();
220 check_ssl(*config, config_enabled);
221 }
222 catch (...) {
223 std::cerr << "init_lan error: checking UCI file /etc/config/nginx\n";
224 ex = std::current_exception();
225 }
226 });
227
228 try {
229 create_lan_listen();
230 }
231 catch (...) {
232 std::cerr << "init_lan error: cannot create listen files of local IPs.\n";
233 ex = std::current_exception();
234 }
235
236 configuring.lock();
237 if (config_enabled) {
238 try {
239 init_uci(*config);
240 }
241 catch (...) {
242 std::cerr << "init_lan error: cannot create " << VAR_UCI_CONF << " from ";
243 std::cerr << UCI_CONF << ".template using UCI file /etc/config/nginx\n";
244 ex = std::current_exception();
245 }
246 }
247
248 thrd.join();
249 if (ex) {
250 std::rethrow_exception(ex);
251 }
252}
253
254void get_env()
255{
256 std::cout << "UCI_CONF="
257 << "'" << UCI_CONF << "'" << std::endl;
258 std::cout << "NGINX_CONF="
259 << "'" << NGINX_CONF << "'" << std::endl;
260 std::cout << "CONF_DIR="
261 << "'" << CONF_DIR << "'" << std::endl;
262 std::cout << "LAN_NAME="
263 << "'" << LAN_NAME << "'" << std::endl;
264 std::cout << "LAN_LISTEN="
265 << "'" << LAN_LISTEN << "'" << std::endl;
266 std::cout << "LAN_SSL_LISTEN="
267 << "'" << LAN_SSL_LISTEN << "'" << std::endl;
268 std::cout << "SSL_SESSION_CACHE_ARG="
269 << "'" << SSL_SESSION_CACHE_ARG(LAN_NAME) << "'" << std::endl;
270 std::cout << "SSL_SESSION_TIMEOUT_ARG="
271 << "'" << SSL_SESSION_TIMEOUT_ARG << "'\n";
272 std::cout << "ADD_SSL_FCT="
273 << "'" << ADD_SSL_FCT << "'" << std::endl;
274 std::cout << "MANAGE_SSL="
275 << "'" << MANAGE_SSL << "'" << std::endl;
276}
277
278auto main(int argc, char* argv[]) -> int
279{
280 // TODO(pst): use std::span when available:
281 auto args = std::basic_string_view<char*>{argv, static_cast<size_t>(argc)};
282
283 auto cmds = std::array{
284 std::array<std::string_view, 2>{"init_lan", ""},
285 std::array<std::string_view, 2>{"get_env", ""},
286 std::array<std::string_view, 2>{
287 ADD_SSL_FCT, "server_name [manager /path/to/ssl_certificate /path/to/ssl_key]"},
288 std::array<std::string_view, 2>{"del_ssl", "server_name [manager]"},
289 std::array<std::string_view, 2>{"check_ssl", ""},
290 };
291
292 try {
293 if (argc == 2 && args[1] == cmds[0][0]) {
294 init_lan();
295 }
296
297 else if (argc == 2 && args[1] == cmds[1][0]) {
298 get_env();
299 }
300
301 else if (argc == 3 && args[1] == cmds[2][0]) {
302 add_ssl_if_needed(std::string{args[2]});
303 }
304
305 // NOLINTNEXTLINE(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers): 6
306 else if (argc == 6 && args[1] == cmds[2][0]) {
307 // NOLINTNEXTLINE(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers): 5
308 add_ssl_if_needed(std::string{args[2]}, args[3], args[4], args[5]);
309 }
310
311 else if (argc == 3 && args[1] == cmds[3][0]) {
312 del_ssl(std::string{args[2]});
313 }
314
315 else if (argc == 4 && args[1] == cmds[3][0]) {
316 del_ssl(std::string{args[2]}, args[3]);
317 }
318
319 else if (argc == 2 && args[1] == cmds[3][0]) // TODO(pst) deprecate
320 {
321 try {
322 auto name = std::string{LAN_NAME};
323 if (del_ssl_legacy(name)) {
324 auto crtpath = std::string{CONF_DIR} + name + ".crt";
325 remove(crtpath.c_str());
326 auto keypath = std::string{CONF_DIR} + name + ".key";
327 remove(keypath.c_str());
328 }
329 }
330 catch (...) { /* do nothing. */
331 }
332 }
333
334 else if (argc == 2 && args[1] == cmds[4][0]) {
335 check_ssl(uci::package{"nginx"});
336 }
337
338 else {
339 std::cerr << "Tool for creating Nginx configuration files (";
340#ifdef VERSION
341 std::cerr << "version " << VERSION << " ";
342#endif
343 std::cerr << "with libuci, ";
344#ifndef NO_UBUS
345 std::cerr << "libubus, ";
346#endif
347 std::cerr << "libopenssl, ";
348#ifndef NO_PCRE
349 std::cerr << "PCRE, ";
350#endif
351 std::cerr << "pthread and libstdcpp)." << std::endl;
352
353 auto usage =
354 std::accumulate(cmds.begin(), cmds.end(), std::string{"usage: "} + *argv + " [",
355 [](const auto& use, const auto& cmd) {
356 return use + std::string{cmd[0]} + (cmd[1].empty() ? "" : " ") +
357 std::string{cmd[1]} + "|";
358 });
359 usage[usage.size() - 1] = ']';
360 std::cerr << usage << std::endl;
361
362 throw std::runtime_error("main error: argument not recognized");
363 }
364
365 return 0;
366 }
367
368 catch (const std::exception& e) {
369 std::cerr << " * " << *argv << " " << e.what() << "\n";
370 }
371
372 catch (...) {
373 std::cerr << " * * " << *argv;
374 perror(" main error");
375 }
376
377 return 1;
378}