Skip to content

Commit b91d5ba

Browse files
committed
Merge branch 'development' into copilot/fix-b5adb213-37cc-4cb3-b094-28ab67b1773a
Signed-off-by: Dominik <[email protected]>
2 parents 7b004b4 + 3766318 commit b91d5ba

File tree

14 files changed

+330
-108
lines changed

14 files changed

+330
-108
lines changed

src/api/docs/content/specs/config.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,8 @@ components:
431431
properties:
432432
cert:
433433
type: string
434+
validity:
435+
type: integer
434436
paths:
435437
type: object
436438
properties:
@@ -769,6 +771,7 @@ components:
769771
restore: true
770772
tls:
771773
cert: "/etc/pihole/tls.pem"
774+
validity: 47
772775
paths:
773776
webroot: "/var/www/html"
774777
webhome: "/admin/"

src/args.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,12 +417,16 @@ void parse_args(int argc, char *argv[])
417417
printf(" RSA with domain: %s --gen-x509 /etc/pihole/tls.pem nanopi.lan rsa\n", argv[0]);
418418
exit(EXIT_FAILURE);
419419
}
420+
// Read config
421+
readFTLconf(&config, false);
422+
420423
// Enable stdout printing
421424
cli_mode = true;
422425
log_ctrl(false, true);
426+
423427
const char *domain = argc > 3 ? argv[3] : "pi.hole";
424428
const bool rsa = argc > 4 && strcasecmp(argv[4], "rsa") == 0;
425-
exit(generate_certificate(argv[2], rsa, domain) ? EXIT_SUCCESS : EXIT_FAILURE);
429+
exit(generate_certificate(argv[2], rsa, domain, config.webserver.tls.validity.v.ui) ? EXIT_SUCCESS : EXIT_FAILURE);
426430
#else
427431
printf("Error: FTL was compiled without TLS support. Certificate generation is not available.\n");
428432
exit(EXIT_FAILURE);

src/config/config.c

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1045,8 +1045,8 @@ void initConfig(struct config *conf)
10451045
conf->webserver.threads.c = validate_stub; // Only type-based checking
10461046

10471047
conf->webserver.headers.k = "webserver.headers";
1048-
conf->webserver.headers.h = "Additional HTTP headers added to the web server responses.\n The headers are added to all responses, including those for the API.\n Note about the default additional headers:\n - X-DNS-Prefetch-Control: off: Usually browsers proactively perform domain name resolution on links that the user may choose to follow. We disable DNS prefetching here.\n - Content-Security-Policy: [...] 'unsafe-inline' is both required by Chart.js styling some elements directly, and index.html containing some inlined Javascript code.\n - X-Frame-Options: DENY: The page can not be displayed in a frame, regardless of the site attempting to do so.\n - X-Xss-Protection: 0: Disables XSS filtering in browsers that support it. This header is usually enabled by default in browsers, and is not recommended as it can hurt the security of the site. (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection).\n - X-Content-Type-Options: nosniff: Marker used by the server to indicate that the MIME types advertised in the Content-Type headers should not be changed and be followed. This allows to opt-out of MIME type sniffing, or, in other words, it is a way to say that the webmasters knew what they were doing. Site security testers usually expect this header to be set.\n - Referrer-Policy: strict-origin-when-cross-origin: A referrer will be sent for same-site origins, but cross-origin requests will send no referrer information.\n The latter four headers are set as expected by https://securityheaders.io";
1049-
conf->webserver.headers.a = cJSON_CreateStringReference("array of HTTP headers");
1048+
conf->webserver.headers.h = "Additional HTTP headers added to the web server responses.\n\n The headers are added to all responses, including those for the API.\n Note about the default additional headers:\n - X-DNS-Prefetch-Control: off: Usually browsers proactively perform domain name resolution on links that the user may choose to follow. We disable DNS prefetching here.\n - Content-Security-Policy: [...] 'unsafe-inline' is both required by Chart.js styling some elements directly, and index.html containing some inlined Javascript code.\n - X-Frame-Options: DENY: The page can not be displayed in a frame, regardless of the site attempting to do so.\n - X-Xss-Protection: 0: Disables XSS filtering in browsers that support it. This header is usually enabled by default in browsers, and is not recommended as it can hurt the security of the site. (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection).\n - X-Content-Type-Options: nosniff: Marker used by the server to indicate that the MIME types advertised in the Content-Type headers should not be changed and be followed. This allows to opt-out of MIME type sniffing, or, in other words, it is a way to say that the webmasters knew what they were doing. Site security testers usually expect this header to be set.\n - Referrer-Policy: strict-origin-when-cross-origin: A referrer will be sent for same-site origins, but cross-origin requests will send no referrer information.\n The latter four headers are set as expected by https://securityheaders.io";
1049+
conf->webserver.headers.a = cJSON_CreateStringReference("An array of HTTP headers");
10501050
conf->webserver.headers.t = CONF_JSON_STRING_ARRAY;
10511051
conf->webserver.headers.f = FLAG_RESTART_FTL;
10521052
conf->webserver.headers.d.json = cJSON_CreateArray();
@@ -1059,28 +1059,33 @@ void initConfig(struct config *conf)
10591059
conf->webserver.headers.c = validate_stub; // Only type-based checking
10601060

10611061
conf->webserver.serve_all.k = "webserver.serve_all";
1062-
conf->webserver.serve_all.h = "Should the web server serve all files in webserver.paths.webroot directory? If disabled, only files within the path defined through webserver.paths.webhome and /api will be served.";
1062+
conf->webserver.serve_all.h = "Should the web server serve all files in webserver.paths.webroot directory?\n\n If disabled, only files within the path defined through webserver.paths.webhome and /api will be served.";
10631063
conf->webserver.serve_all.t = CONF_BOOL;
10641064
conf->webserver.serve_all.d.b = false;
10651065
conf->webserver.serve_all.c = validate_stub;
10661066

1067-
// sub-struct session
1067+
conf->webserver.tls.validity.k = "webserver.tls.validity";
1068+
conf->webserver.tls.validity.h = "Number of days the automatically generated self-signed TLS/SSL certificate will be valid for.\n\n Defaults to 47 days. A minimum of 7 days is enforced.\n Some devices may enforce shorter validity ranges. Note that defining a lower validity range may require you to accept the self-signed certificate more often in your browser.\n Pi-hole will regenerate certificates it created itself two days prior to expiration. If you are using your own certificate, you need to regenerate it yourself. In this case, it is advised to set the validity range to 0 days, so that Pi-hole does not try to regenerate your certificate. If you set the validity range to 0 days and still try to generate a certificate, Pi-hole will set a fixed validity range of roughly 30 years for the certificate.";
1069+
conf->webserver.tls.validity.t = CONF_UINT;
1070+
conf->webserver.tls.validity.d.ui = 47; // 47 days
1071+
conf->webserver.tls.validity.c = validate_ui_min_7_or_0;
1072+
1073+
// sub-struct webserver.session
10681074
conf->webserver.session.timeout.k = "webserver.session.timeout";
1069-
conf->webserver.session.timeout.h = "Session timeout in seconds. If a session is inactive for more than this time, it will be terminated. Sessions are continuously refreshed by the web interface, preventing sessions from timing out while the web interface is open.\n\n This option may also be used to make logins persistent for long times, e.g. 86400 seconds (24 hours), 604800 seconds (7 days) or 2592000 seconds (30 days). Note that the total number of concurrent sessions is limited so setting this value too high may result in users being rejected and unable to log in if there are already too many sessions active.";
1075+
conf->webserver.session.timeout.h = "Session timeout in seconds.\n\n If a session is inactive for more than this time, it will be terminated. Sessions are continuously refreshed by the web interface, preventing sessions from timing out while the web interface is open.\n\n This option may also be used to make logins persistent for long times, e.g. 86400 seconds (24 hours), 604800 seconds (7 days) or 2592000 seconds (30 days). Note that the total number of concurrent sessions is limited so setting this value too high may result in users being rejected and unable to log in if there are already too many sessions active.";
10701076
conf->webserver.session.timeout.a = cJSON_CreateStringReference("A positive integer value in seconds");
10711077
conf->webserver.session.timeout.t = CONF_UINT;
10721078
conf->webserver.session.timeout.d.ui = 1800u;
10731079
conf->webserver.session.timeout.c = validate_stub; // Only type-based checking
10741080

10751081
conf->webserver.session.restore.k = "webserver.session.restore";
1076-
conf->webserver.session.restore.h = "Should Pi-hole backup and restore sessions from the database? This is useful if you want to keep your sessions after a restart of the web interface.";
1077-
conf->webserver.session.restore.a = cJSON_CreateStringReference("true or false");
1082+
conf->webserver.session.restore.h = "Should Pi-hole backup and restore sessions from the database?\n\n This is useful if you want to keep your sessions after a restart of the web interface.";
10781083
conf->webserver.session.restore.t = CONF_BOOL;
10791084
conf->webserver.session.restore.d.b = true;
10801085
conf->webserver.session.restore.c = validate_stub; // Only type-based checking
10811086

10821087
conf->webserver.tls.cert.k = "webserver.tls.cert";
1083-
conf->webserver.tls.cert.h = "Path to the TLS (SSL) certificate file. All directories along the path must be readable and accessible by the user running FTL (typically 'pihole'). This option is only required when at least one of webserver.port is TLS. The file must be in PEM format, and it must have both, private key and certificate (the *.pem file created must contain a 'CERTIFICATE' section as well as a 'RSA PRIVATE KEY' section).\n\n The *.pem file can be created using `cp server.crt server.pem && cat server.key >> server.pem` if you have these files instead";
1088+
conf->webserver.tls.cert.h = "Path to the TLS (SSL) certificate file.\n\n All directories along the path must be readable and accessible by the user running FTL (typically 'pihole'). This option is only required when at least one of webserver.port is TLS. The file must be in PEM format, and it must have both, private key and certificate (the *.pem file created must contain a 'CERTIFICATE' section as well as a 'RSA PRIVATE KEY' section).\n\n The *.pem file can be created using `cp server.crt server.pem && cat server.key >> server.pem` if you have these files instead";
10841089
conf->webserver.tls.cert.a = cJSON_CreateStringReference("A valid TLS certificate file (*.pem)");
10851090
conf->webserver.tls.cert.f = FLAG_RESTART_FTL;
10861091
conf->webserver.tls.cert.t = CONF_STRING;

src/config/config.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ struct config {
255255
} session;
256256
struct {
257257
struct conf_item cert;
258+
struct conf_item validity;
258259
} tls;
259260
struct {
260261
struct conf_item webroot;

src/config/validator.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,17 @@ bool validate_dns_revServers(union conf_value *val, const char *key, char err[VA
545545
return true;
546546
}
547547

548+
bool validate_ui_min_7_or_0(union conf_value *val, const char *key, char err[VALIDATOR_ERRBUF_LEN])
549+
{
550+
if(val->ui < 7 && val->ui != 0)
551+
{
552+
snprintf(err, VALIDATOR_ERRBUF_LEN, "%s: cannot be lower than 7", key);
553+
return false;
554+
}
555+
556+
return true;
557+
}
558+
548559
// Sanitize the dns.hosts array
549560
// This function normalizes whitespace formatting in the dns.hosts entries
550561
// to ensure consistent formatting when saving to pihole.toml

src/config/validator.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ bool validate_filepath_empty(union conf_value *val, const char *key, char err[VA
2626
bool validate_filepath_dash(union conf_value *val, const char *key, char err[VALIDATOR_ERRBUF_LEN]);
2727
bool validate_regex_array(union conf_value *val, const char *key, char err[VALIDATOR_ERRBUF_LEN]);
2828
bool validate_dns_revServers(union conf_value *val, const char *key, char err[VALIDATOR_ERRBUF_LEN]);
29+
bool validate_ui_min_7_or_0(union conf_value *val, const char *key, char err[VALIDATOR_ERRBUF_LEN]);
2930
void sanitize_dns_hosts(union conf_value *val);
3031

3132
#endif // CONFIG_VALIDATOR_H

src/dnsmasq_interface.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3341,6 +3341,13 @@ void FTL_fork_and_bind_sockets(struct passwd *ent_pw, bool dnsmasq_start)
33413341
exit(EXIT_FAILURE);
33423342
}
33433343

3344+
// Start webserver thread
3345+
if(pthread_create( &threads[WEBSERVER], &attr, webserver_thread, NULL ) != 0)
3346+
{
3347+
log_crit("Unable to create webserver thread. Exiting...");
3348+
exit(EXIT_FAILURE);
3349+
}
3350+
33443351
// Chown files if FTL started as user root but a dnsmasq config
33453352
// option states to run as a different user/group (e.g. "nobody")
33463353
if(getuid() == 0)

src/enums.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ enum thread_types {
247247
NTP_CLIENT,
248248
NTP_SERVER4,
249249
NTP_SERVER6,
250+
WEBSERVER,
250251
THREADS_MAX
251252
} __attribute__ ((packed));
252253

@@ -331,7 +332,9 @@ enum cert_check {
331332
CERT_CANNOT_PARSE_KEY,
332333
CERT_DOMAIN_MISMATCH,
333334
CERT_DOMAIN_MATCH,
334-
CERT_OKAY
335+
CERT_NOT_YET_VALID,
336+
CERT_EXPIRES_SOON,
337+
CERT_OKAY,
335338
} __attribute__ ((packed));
336339

337340
enum http_method {

src/signals.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ const char * const thread_names[THREADS_MAX] = {
4242
"ntp-client",
4343
"ntp-server4",
4444
"ntp-server6",
45+
"webserver",
4546
};
4647

4748
// Return the (null-terminated) name of the calling thread

src/webserver/webserver.c

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
#include "database/message-table.h"
3131
// create_cli_password()
3232
#include "config/password.h"
33+
// thread_names
34+
#include "signals.h"
3335

3436
// Server context handle
3537
static struct mg_context *ctx = NULL;
@@ -649,7 +651,7 @@ void http_init(void)
649651
// Try to generate certificate if not present
650652
if(!file_readable(config.webserver.tls.cert.v.s))
651653
{
652-
if(generate_certificate(config.webserver.tls.cert.v.s, false, config.webserver.domain.v.s))
654+
if(generate_certificate(config.webserver.tls.cert.v.s, false, config.webserver.domain.v.s, config.webserver.tls.validity.v.ui))
653655
{
654656
log_info("Created SSL/TLS certificate for %s at %s",
655657
config.webserver.domain.v.s, config.webserver.tls.cert.v.s);
@@ -878,3 +880,58 @@ void http_terminate(void)
878880
if(webheaders != NULL)
879881
free(webheaders);
880882
}
883+
884+
static void restart_http(void)
885+
{
886+
// Stop the server
887+
http_terminate();
888+
889+
// Reinitialize the webserver
890+
http_init();
891+
}
892+
893+
void *webserver_thread(void *val)
894+
{
895+
(void)val;
896+
// Set thread name
897+
prctl(PR_SET_NAME, thread_names[WEBSERVER], 0, 0, 0);
898+
899+
// Initial delay until we check the certificate for the first time
900+
thread_sleepms(WEBSERVER, 2000);
901+
902+
while(!killed)
903+
{
904+
// Check if the certificate is about to expire soon
905+
const enum cert_check status = cert_currently_valid(config.webserver.tls.cert.v.s, 2);
906+
907+
if(status == CERT_EXPIRES_SOON &&
908+
config.webserver.tls.validity.v.ui > 0)
909+
{
910+
if(is_pihole_certificate(config.webserver.tls.cert.v.s))
911+
{
912+
log_info("TLS certificate at %s is about to expire soon, generating new one",
913+
config.webserver.tls.cert.v.s);
914+
generate_certificate(config.webserver.tls.cert.v.s, false,
915+
config.webserver.domain.v.s,
916+
config.webserver.tls.validity.v.ui);
917+
918+
log_info("Restarting HTTP server");
919+
restart_http();
920+
921+
log_info("Done. The new certificate is valid for %u days",
922+
config.webserver.tls.validity.v.ui);
923+
}
924+
else
925+
{
926+
log_err("TLS certificate at %s is about to expire soon, but it is not a Pi-hole certificate. Please renew it manually!",
927+
config.webserver.tls.cert.v.s);
928+
}
929+
}
930+
931+
// Idle for 1 day (24 hours)
932+
thread_sleepms(WEBSERVER, 86400000);
933+
}
934+
935+
log_info("Terminating webserver thread");
936+
return NULL;
937+
}

0 commit comments

Comments
 (0)