Merge pull request 'develop' (#1) from develop into main
Prepare Build and Testing executable binary / Prepare-Build-Testing-With-Make (push) Successful in 5s
Prepare Build and Testing executable binary / Prepare-Build-Testing-With-CMake (push) Successful in 8s
Prepare Build and Testing executable binary / Prepare-Build-Testing-With-Meson (push) Successful in 7s
Prepare Build and Testing executable binary / Prepare-Build-Testing-With-Make (push) Successful in 5s
Prepare Build and Testing executable binary / Prepare-Build-Testing-With-CMake (push) Successful in 8s
Prepare Build and Testing executable binary / Prepare-Build-Testing-With-Meson (push) Successful in 7s
Reviewed-on: https://gitea.home.com:3000/TolaMironcenko/dns_service_c/pulls/1
This commit was merged in pull request #1.
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 DNS Service Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,292 @@
|
||||
# DNS Service
|
||||
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
|
||||
A lightweight DNS management service written in C with a web-based interface for configuring DNS settings and managing dnsmasq configuration.
|
||||
|
||||
## Features
|
||||
|
||||
- **Web Interface**: Modern responsive web UI for DNS configuration
|
||||
- **DNS Settings Management**: Configure upstream DNS servers, local domains, and cache settings
|
||||
- **DHCP Configuration**: Enable/disable DHCP with range configuration
|
||||
- **File Management**: Direct editing of configuration files
|
||||
- **SSL/TLS Support**: Optional HTTPS with certificate configuration
|
||||
- **JSON Configuration**: Easy-to-manage JSON-based configuration
|
||||
- **Cross-platform**: Supports multiple build systems (Make, CMake, Meson)
|
||||
|
||||
## Architecture
|
||||
|
||||
The service consists of:
|
||||
- **HTTP/HTTPS Server**: Built on Mongoose web server
|
||||
- **REST API**: JSON-based API for configuration management
|
||||
- **Web Interface**: Bootstrap-based responsive UI
|
||||
- **Configuration Parser**: Custom dnsmasq config parser
|
||||
- **File Operations**: Safe file reading/writing utilities
|
||||
- **Settings Management**: Direct manipulation of dnsmasq configuration files
|
||||
- Updates dnsmasq main configuration (`/etc/dnsmasq.conf`)
|
||||
- Manages hosts file (`/etc/dnsmasq.conf.d/hosts`)
|
||||
- Manages resolv.conf (`/etc/dnsmasq.conf.d/resolv.conf`)
|
||||
- Automatically restarts dnsmasq service after configuration changes
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| GET | `/api/settings` | Get current DNS settings |
|
||||
| POST | `/api/settings` | Update DNS settings (cache_size, resolv servers, hosts) and restart dnsmasq |
|
||||
| GET | `/api/settings/file` | Get raw configuration file |
|
||||
| POST | `/api/settings/file` | Update configuration file |
|
||||
| GET | `/#` | Serve web interface |
|
||||
|
||||
## Requirements
|
||||
|
||||
### Build Dependencies
|
||||
- GCC compiler with C23 support
|
||||
- OpenSSL development libraries
|
||||
- pthread library
|
||||
|
||||
### System Dependencies
|
||||
- dnsmasq (for DNS service functionality)
|
||||
- SSL certificates (for HTTPS support)
|
||||
- System initialization service (systemd, openrc, rc, finit, or init)
|
||||
- Appropriate privileges to restart system services
|
||||
|
||||
## Installation
|
||||
|
||||
### Using Make
|
||||
```bash
|
||||
make
|
||||
```
|
||||
|
||||
### Using CMake
|
||||
```bash
|
||||
mkdir build && cd build
|
||||
cmake ..
|
||||
make
|
||||
```
|
||||
|
||||
### Using Meson
|
||||
```bash
|
||||
meson setup builddir
|
||||
meson compile -C builddir
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Edit `config.json` to configure the service:
|
||||
|
||||
```json
|
||||
{
|
||||
"address": "0.0.0.0:8321",
|
||||
"tls_address": "0.0.0.0:8322",
|
||||
"web": "./web",
|
||||
"tls_enabled": true,
|
||||
"tls_cert": "/path/to/certificate.crt",
|
||||
"tls_key": "/path/to/private.key",
|
||||
"system_initialization": "systemd",
|
||||
"dnsmasq_config": "/etc/dnsmasq.conf"
|
||||
}
|
||||
```
|
||||
|
||||
### Configuration Parameters
|
||||
|
||||
- `address`: HTTP server bind address and port
|
||||
- `tls_address`: HTTPS server bind address and port
|
||||
- `web`: Path to web interface files
|
||||
- `tls_enabled`: Enable/disable HTTPS support
|
||||
- `tls_cert`: Path to SSL certificate file
|
||||
- `tls_key`: Path to SSL private key file
|
||||
- `system_initialization`: System initialization type for restarting dnsmasq service
|
||||
- `"systemd"`: Uses `systemctl restart dnsmasq`
|
||||
- `"rc"` or `"openrc"`: Uses `rc restart dnsmasq`
|
||||
- `"finit"`: Uses `initctl restart dnsmasq`
|
||||
- `"init"`: Uses `/etc/init.d/dnsmasq restart`
|
||||
- `dnsmasq_config`: Path to dnsmasq configuration file
|
||||
|
||||
## Usage
|
||||
|
||||
### Starting the Service
|
||||
```bash
|
||||
./dns_service
|
||||
```
|
||||
|
||||
The service will start HTTP server on the configured address (default: `0.0.0.0:8321`) and optionally HTTPS server if TLS is enabled.
|
||||
|
||||
### Web Interface
|
||||
|
||||
Navigate to `http://localhost:8321` (or your configured address) to access the web interface where you can:
|
||||
|
||||
- Configure upstream DNS servers (resolv)
|
||||
- Manage local domain mappings (hosts)
|
||||
- Enable/disable DHCP with range settings
|
||||
- Adjust DNS cache size
|
||||
- Edit configuration files directly
|
||||
- Save settings directly to dnsmasq configuration
|
||||
- Automatic dnsmasq service restart after configuration changes
|
||||
|
||||
### API Usage
|
||||
|
||||
#### Get Current Settings
|
||||
```bash
|
||||
curl -X GET http://localhost:8321/api/settings
|
||||
```
|
||||
|
||||
#### Update Settings
|
||||
```bash
|
||||
curl -X POST http://localhost:8321/api/settings \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"cache_size": 1000,
|
||||
"resolv": ["8.8.8.8", "8.8.4.4"],
|
||||
"hosts": {
|
||||
"192.168.1.1": "router.local",
|
||||
"192.168.1.100": ["server1.local", "server1"]
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
The POST `/api/settings` endpoint accepts the following parameters:
|
||||
- `cache_size` (integer): DNS cache size for dnsmasq
|
||||
- `resolv` (array of strings): Upstream DNS servers
|
||||
- `hosts` (object): Local DNS mappings where keys are IP addresses and values are either single hostnames (string) or arrays of hostnames
|
||||
|
||||
**Note**: After successfully updating the configuration files, the service automatically restarts dnsmasq to apply the changes. The restart method depends on the `system_initialization` setting in `config.json`.
|
||||
|
||||
#### Settings Configuration Details
|
||||
|
||||
**Cache Size (`cache_size`)**
|
||||
- Configures the DNS cache size in dnsmasq
|
||||
- Updates the `cache-size` parameter in `/etc/dnsmasq.conf`
|
||||
- Accepts integer values (recommended: 150-10000 depending on system resources)
|
||||
|
||||
**Upstream DNS Servers (`resolv`)**
|
||||
- Array of DNS server IP addresses to use for upstream resolution
|
||||
- Creates/updates `/etc/dnsmasq.conf.d/resolv.conf` with `nameserver` entries
|
||||
- Example: `["8.8.8.8", "1.1.1.1", "8.8.4.4"]`
|
||||
|
||||
**Local Hosts (`hosts`)**
|
||||
- Object mapping IP addresses to hostnames for local DNS resolution
|
||||
- Creates/updates `/etc/dnsmasq.conf.d/hosts` file
|
||||
- Supports both single hostnames and multiple hostnames per IP
|
||||
- Format:
|
||||
- Single hostname: `"192.168.1.1": "router.local"`
|
||||
- Multiple hostnames: `"192.168.1.100": ["server1.local", "server1"]`
|
||||
|
||||
## Development
|
||||
|
||||
### Project Structure
|
||||
```
|
||||
dns_service_c/
|
||||
├── src/
|
||||
│ ├── lib/ # Third-party libraries
|
||||
│ │ ├── mongoose.c # HTTP server
|
||||
│ │ ├── cJSON.c # JSON parser
|
||||
│ │ └── ...
|
||||
│ ├── main.c # Application entry point
|
||||
│ ├── config.c # Configuration management
|
||||
│ ├── settings.c # DNS settings handlers (GET)
|
||||
│ ├── set_settings.c # DNS settings handlers (POST)
|
||||
│ ├── set_dnsmasq_settings_field.c # Update dnsmasq config fields
|
||||
│ ├── set_hosts.c # Manage hosts file
|
||||
│ ├── set_resolvs.c # Manage resolv.conf
|
||||
│ ├── restart_dnsmasq.c # Restart dnsmasq service
|
||||
│ ├── settings.h # Settings function declarations
|
||||
│ ├── restart_dnsmasq.h # Restart function declarations
|
||||
│ └── ...
|
||||
├── web/ # Web interface files
|
||||
│ ├── index.html
|
||||
│ ├── css/
|
||||
│ └── js/
|
||||
├── config.json # Service configuration
|
||||
└── README.md
|
||||
```
|
||||
|
||||
### Building for Development
|
||||
```bash
|
||||
# With debug symbols
|
||||
make CFLAGS="-Wall -g -std=gnu2x -DDEBUG"
|
||||
|
||||
# Clean build
|
||||
make clean && make
|
||||
```
|
||||
|
||||
### Code Style
|
||||
The project uses `.clang-format` for consistent code formatting:
|
||||
```bash
|
||||
clang-format -i src/*.c src/*.h
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- The service runs with system privileges to modify DNS configuration files
|
||||
- HTTPS is strongly recommended for production deployments
|
||||
- Ensure proper file permissions on certificate files
|
||||
- The service directly modifies system files (`/etc/dnsmasq.conf`, `/etc/dnsmasq.conf.d/hosts`, `/etc/dnsmasq.conf.d/resolv.conf`)
|
||||
- Consider firewall rules to restrict access to management interface
|
||||
- Validate all input data to prevent configuration file corruption
|
||||
- Backup configuration files before making changes
|
||||
- Ensure the service has appropriate write permissions to dnsmasq configuration directories
|
||||
- The service requires privileges to restart the dnsmasq service via system commands
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Permission denied when accessing dnsmasq config**
|
||||
- Ensure the service has read/write permissions to the dnsmasq configuration file
|
||||
- Run with appropriate privileges or adjust file permissions
|
||||
- Check permissions for `/etc/dnsmasq.conf.d/` directory
|
||||
|
||||
3. **Failed to write settings**
|
||||
- Verify write permissions to `/etc/dnsmasq.conf`, `/etc/dnsmasq.conf.d/hosts`, and `/etc/dnsmasq.conf.d/resolv.conf`
|
||||
- Ensure the `/etc/dnsmasq.conf.d/` directory exists
|
||||
- Check disk space availability
|
||||
|
||||
4. **Failed to restart dnsmasq**
|
||||
- Ensure the service has appropriate privileges to restart system services
|
||||
- Verify the `system_initialization` parameter in `config.json` matches your system
|
||||
- Supported initialization systems: rc, openrc, systemd, finit, init
|
||||
- Check that dnsmasq service is properly installed and configured on the system
|
||||
|
||||
5. **SSL/TLS certificate errors**
|
||||
- Verify certificate and key file paths in `config.json`
|
||||
- Ensure certificate files have correct permissions
|
||||
- Check certificate validity and format
|
||||
|
||||
6. **Port binding errors**
|
||||
- Verify ports are not already in use
|
||||
- Check firewall settings
|
||||
- Ensure proper privileges for binding to privileged ports
|
||||
|
||||
7. **Settings not persisting after dnsmasq restart**
|
||||
- Ensure dnsmasq is configured to read from `/etc/dnsmasq.conf.d/` directory
|
||||
- Verify the main dnsmasq configuration includes the conf-dir directive
|
||||
- Check that temporary files are properly cleaned up after configuration updates
|
||||
|
||||
### Logging
|
||||
The service uses Mongoose's built-in logging. Set log level in code or use debug build for verbose output.
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch
|
||||
3. Make your changes following the existing code style
|
||||
4. Test your changes thoroughly
|
||||
5. Submit a pull request
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
||||
|
||||
### Third-party Libraries
|
||||
|
||||
This project includes the following third-party libraries:
|
||||
|
||||
- **Mongoose**: HTTP/WebSocket server library
|
||||
- **cJSON**: JSON parser library
|
||||
|
||||
Please refer to their respective licenses for usage terms.
|
||||
|
||||
## Support
|
||||
|
||||
For issues and questions, please check the project's issue tracker or documentation.
|
||||
+8
-8
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"address": "0.0.0.0:8321",
|
||||
"tls_address": "0.0.0.0:8322",
|
||||
"web": "./web",
|
||||
"tls_enabled": true,
|
||||
"tls_cert": "/home/tola/certs/localhost/localhost.crt",
|
||||
"tls_key": "/home/tola/certs/localhost/localhost.key",
|
||||
"system_initialization": "rc",
|
||||
"dnsmasq_config": "/etc/dnsmasq.conf"
|
||||
"address": "0.0.0.0:8321",
|
||||
"tls_address": "0.0.0.0:8322",
|
||||
"web": "./web",
|
||||
"tls_enabled": true,
|
||||
"tls_cert": "/home/tola/certs/localhost/localhost.crt",
|
||||
"tls_key": "/home/tola/certs/localhost/localhost.key",
|
||||
"system_initialization": "systemd",
|
||||
"dnsmasq_config": "/etc/dnsmasq.conf"
|
||||
}
|
||||
|
||||
@@ -17,6 +17,10 @@ executable(
|
||||
'src/set_settings_file.c',
|
||||
'src/get_settings_file.c',
|
||||
'src/static.c',
|
||||
'src/set_hosts.c',
|
||||
'src/set_resolvs.c',
|
||||
'src/set_dnsmasq_settings_field.c',
|
||||
'src/restart_dnsmasq.c',
|
||||
include_directories: include_dirs,
|
||||
dependencies: deps,
|
||||
)
|
||||
|
||||
@@ -43,7 +43,6 @@ char *write_file(const char *path, const char *data) {
|
||||
perror("Error opening file");
|
||||
return nullptr;
|
||||
}
|
||||
printf("%s\n%s\n", data, path);
|
||||
if (fprintf(fp, "%s", data) < 0) {
|
||||
perror("Error write file");
|
||||
return nullptr;
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
#include "restart_dnsmasq.h"
|
||||
#include "ConfigManager.h"
|
||||
#include "config.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
char *restart_dnsmasq() {
|
||||
int res = -2;
|
||||
char *system_initialization = cm_get_parameter_as_string(config, "system_initialization");
|
||||
if (!system_initialization) {
|
||||
res = -1;
|
||||
}
|
||||
if (!strcmp(system_initialization, "rc")) {
|
||||
res = system("rc restart dnsmasq");
|
||||
}
|
||||
if (!strcmp(system_initialization, "openrc")) {
|
||||
res = system("rc restart dnsmasq");
|
||||
}
|
||||
if (!strcmp(system_initialization, "systemd")) {
|
||||
res = system("systemctl restart dnsmasq");
|
||||
}
|
||||
if (!strcmp(system_initialization, "finit")) {
|
||||
res = system("initctl restart dnsmasq");
|
||||
}
|
||||
if (!strcmp(system_initialization, "init")) {
|
||||
res = system("/etc/init.d/dnsmasq restart");
|
||||
}
|
||||
if (res != 0) {
|
||||
return strdup("Failed to restart dnsmasq");
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
char *restart_dnsmasq();
|
||||
@@ -0,0 +1,44 @@
|
||||
#include "config.h"
|
||||
#include "settings.h"
|
||||
|
||||
#define DNSMASQ_CONFIG_FILE_TMP "dnsmasq.conf.tmp"
|
||||
|
||||
char *set_dnsmasq_settings_field(const char *key, cJSON *setting) {
|
||||
char *filename = cm_get_parameter_as_string(config, "dnsmasq_config");
|
||||
if (!filename) {
|
||||
return "Failed to read config file";
|
||||
}
|
||||
FILE *dnsmasq_config_file = fopen(filename, "r");
|
||||
FILE *dnsmasq_config_file_tmp = fopen(DNSMASQ_CONFIG_FILE_TMP, "w");
|
||||
|
||||
if (dnsmasq_config_file == nullptr || dnsmasq_config_file_tmp == nullptr) {
|
||||
return "Failed to open config file";
|
||||
}
|
||||
|
||||
char line[256];
|
||||
char *new_str = malloc(1024);
|
||||
snprintf(new_str, 1024, "%s=%d", key, setting->valueint);
|
||||
|
||||
while (fgets(line, sizeof(line), dnsmasq_config_file)) {
|
||||
if (strstr(line, key) != nullptr || strcmp(line, key) == 0) {
|
||||
fputs(new_str, dnsmasq_config_file_tmp);
|
||||
fputs("\n", dnsmasq_config_file_tmp);
|
||||
} else {
|
||||
fputs(line, dnsmasq_config_file_tmp);
|
||||
}
|
||||
}
|
||||
|
||||
fclose(dnsmasq_config_file);
|
||||
fclose(dnsmasq_config_file_tmp);
|
||||
free(new_str);
|
||||
|
||||
if (remove(filename) != 0) {
|
||||
remove(DNSMASQ_CONFIG_FILE_TMP);
|
||||
return "Failed to remove config file";
|
||||
}
|
||||
if (rename(DNSMASQ_CONFIG_FILE_TMP, filename) != 0) {
|
||||
remove(DNSMASQ_CONFIG_FILE_TMP);
|
||||
return "Failed to rename config file";
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
#include "settings.h"
|
||||
|
||||
#define DNSMASQ_HOSTS_FILE_TMP "hosts.tmp"
|
||||
|
||||
char *set_hosts(cJSON *hosts) {
|
||||
FILE *hosts_file_tmp = fopen(DNSMASQ_HOSTS_FILE_TMP, "w");
|
||||
if (!hosts_file_tmp) {
|
||||
return "can't open hosts file";
|
||||
}
|
||||
|
||||
cJSON *host = hosts->child;
|
||||
while (host) {
|
||||
if (!cJSON_IsString(host) && !cJSON_IsArray(host)) {
|
||||
remove(DNSMASQ_HOSTS_FILE_TMP);
|
||||
return "Invalid hosts";
|
||||
}
|
||||
if (cJSON_IsString(host)) {
|
||||
fprintf(hosts_file_tmp, "%s %s\n", host->string, host->valuestring);
|
||||
}
|
||||
if (cJSON_IsArray(host)) {
|
||||
int arrsize = cJSON_GetArraySize(host);
|
||||
if (arrsize > 0) {
|
||||
fprintf(hosts_file_tmp, "%s ", host->string);
|
||||
}
|
||||
for (int i = 0; i < arrsize; i++) {
|
||||
cJSON *domain = cJSON_GetArrayItem(host, i);
|
||||
if (domain && cJSON_IsString(domain)) {
|
||||
fprintf(hosts_file_tmp, "%s ", domain->valuestring);
|
||||
}
|
||||
}
|
||||
fprintf(hosts_file_tmp, "\n");
|
||||
}
|
||||
host = host->next;
|
||||
}
|
||||
|
||||
fclose(hosts_file_tmp);
|
||||
|
||||
if (rename(DNSMASQ_HOSTS_FILE_TMP, "/etc/dnsmasq.conf.d/hosts") != 0) {
|
||||
remove(DNSMASQ_HOSTS_FILE_TMP);
|
||||
return "Failed to rename hosts file";
|
||||
}
|
||||
remove(DNSMASQ_HOSTS_FILE_TMP);
|
||||
return nullptr;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
#include "settings.h"
|
||||
|
||||
#define DNSMASQ_RESOLV_FILE_TMP "resolv.conf.tmp"
|
||||
|
||||
char *set_resolvs(cJSON *resolvs) {
|
||||
FILE *resolvs_file_tmp = fopen(DNSMASQ_RESOLV_FILE_TMP, "w");
|
||||
if (!resolvs_file_tmp) {
|
||||
return "Failed to open resolv file";
|
||||
}
|
||||
|
||||
cJSON *resolv = resolvs->child;
|
||||
while (resolv) {
|
||||
if (!cJSON_IsString(resolv)) {
|
||||
remove(DNSMASQ_RESOLV_FILE_TMP);
|
||||
return "Invalid resolv";
|
||||
}
|
||||
fprintf(resolvs_file_tmp, "nameserver %s\n", resolv->valuestring);
|
||||
resolv = resolv->next;
|
||||
}
|
||||
|
||||
fclose(resolvs_file_tmp);
|
||||
|
||||
if (rename(DNSMASQ_RESOLV_FILE_TMP, "/etc/dnsmasq.conf.d/resolv.conf") != 0) {
|
||||
remove(DNSMASQ_RESOLV_FILE_TMP);
|
||||
return "Failed to rename resolv file";
|
||||
}
|
||||
remove(DNSMASQ_RESOLV_FILE_TMP);
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1,4 +1,71 @@
|
||||
#include "settings.h"
|
||||
#include "restart_dnsmasq.h"
|
||||
|
||||
void set_settings(struct mg_connection *c, struct mg_http_message *hm) {
|
||||
cJSON *jsonbody = cJSON_Parse(hm->body.buf);
|
||||
if (!jsonbody) {
|
||||
mg_http_reply(c, 500, HTTP_HEADERS, "{%m:%m}", MG_ESC("error"),
|
||||
MG_ESC("can't parse JSON body"));
|
||||
return;
|
||||
}
|
||||
|
||||
cJSON *cache_size = cJSON_GetObjectItemCaseSensitive(jsonbody, "cache_size");
|
||||
if (!cache_size || !cJSON_IsNumber(cache_size)) {
|
||||
mg_http_reply(c, 500, HTTP_HEADERS, "{%m:%m}", MG_ESC("error"),
|
||||
MG_ESC("invalid cache_size"));
|
||||
cJSON_Delete(jsonbody);
|
||||
return;
|
||||
}
|
||||
|
||||
char *result_set_dnsmasq_settings_field =
|
||||
set_dnsmasq_settings_field("cache-size", cache_size);
|
||||
if (result_set_dnsmasq_settings_field) {
|
||||
mg_http_reply(c, 200, HTTP_HEADERS, "{%m:%m}", MG_ESC("error"),
|
||||
MG_ESC(result_set_dnsmasq_settings_field));
|
||||
cJSON_Delete(jsonbody);
|
||||
return;
|
||||
}
|
||||
|
||||
cJSON *resolvs = cJSON_GetObjectItemCaseSensitive(jsonbody, "resolv");
|
||||
if (!resolvs || !cJSON_IsArray(resolvs)) {
|
||||
mg_http_reply(c, 500, HTTP_HEADERS, "{%m:%m}", MG_ESC("error"),
|
||||
MG_ESC("invalid resolv"));
|
||||
cJSON_Delete(jsonbody);
|
||||
return;
|
||||
}
|
||||
|
||||
char *result_set_resolvs = set_resolvs(resolvs);
|
||||
if (result_set_resolvs) {
|
||||
mg_http_reply(c, 500, HTTP_HEADERS, "{%m:%m}", MG_ESC("error"),
|
||||
MG_ESC(result_set_resolvs));
|
||||
cJSON_Delete(jsonbody);
|
||||
return;
|
||||
}
|
||||
|
||||
cJSON *hosts = cJSON_GetObjectItemCaseSensitive(jsonbody, "hosts");
|
||||
if (!hosts || !cJSON_IsObject(hosts)) {
|
||||
mg_http_reply(c, 500, HTTP_HEADERS, "{%m:%m}", MG_ESC("error"),
|
||||
MG_ESC("invalid hosts"));
|
||||
cJSON_Delete(jsonbody);
|
||||
return;
|
||||
}
|
||||
|
||||
char *result_set_hosts = set_hosts(hosts);
|
||||
if (result_set_hosts) {
|
||||
mg_http_reply(c, 500, HTTP_HEADERS, "{%m:%m}", MG_ESC("error"),
|
||||
MG_ESC(result_set_hosts));
|
||||
cJSON_Delete(jsonbody);
|
||||
return;
|
||||
}
|
||||
|
||||
char *error = restart_dnsmasq();
|
||||
if (error) {
|
||||
mg_http_reply(c, 500, HTTP_HEADERS, "{%m:%m}", MG_ESC("error"),
|
||||
MG_ESC(error));
|
||||
free(error);
|
||||
return;
|
||||
}
|
||||
|
||||
mg_http_reply(c, 200, HTTP_HEADERS, "%s", hm->body.buf);
|
||||
cJSON_Delete(jsonbody);
|
||||
}
|
||||
|
||||
+40
-9
@@ -1,21 +1,52 @@
|
||||
#include "settings_file.h"
|
||||
#include <file.h>
|
||||
#include "cJSON.h"
|
||||
#include "config.h"
|
||||
#include "restart_dnsmasq.h"
|
||||
#include "settings_file.h"
|
||||
|
||||
void set_settings_file(struct mg_connection *c, struct mg_http_message *hm) {
|
||||
char *filename = cm_get_parameter_as_string(config, "dnsmasq_config");
|
||||
if (!filename) {
|
||||
mg_http_reply(c, 500, HTTP_HEADERS, "{%m:%m}", MG_ESC("error"),
|
||||
MG_ESC("can't read config file"));
|
||||
return;
|
||||
mg_http_reply(c, 500, HTTP_HEADERS, "{%m:%m}", MG_ESC("error"),
|
||||
MG_ESC("can't read config file"));
|
||||
return;
|
||||
}
|
||||
char *res = write_file(cm_get_parameter_as_string(config, "dnsmasq_config"), hm->body.buf);
|
||||
char *res = write_file(cm_get_parameter_as_string(config, "dnsmasq_config"),
|
||||
hm->body.buf);
|
||||
if (!res) {
|
||||
mg_http_reply(c, 500, HTTP_HEADERS, "{%m:%m}", MG_ESC("error"),
|
||||
MG_ESC("can't write file"));
|
||||
return;
|
||||
mg_http_reply(c, 500, HTTP_HEADERS, "{%m:%m}", MG_ESC("error"),
|
||||
MG_ESC("can't write file"));
|
||||
return;
|
||||
}
|
||||
mg_http_reply(c, 200, "Content-Type: text/plain\r\nAccess-Control-Allow-Origin: *\r\n", res);
|
||||
char *error = restart_dnsmasq();
|
||||
if (error) {
|
||||
mg_http_reply(c, 500, HTTP_HEADERS, "{%m:%m}", MG_ESC("error"),
|
||||
MG_ESC(error));
|
||||
free(error);
|
||||
return;
|
||||
}
|
||||
cJSON *response = cJSON_CreateObject();
|
||||
if (!response) {
|
||||
mg_http_reply(c, 500, HTTP_HEADERS, "{%m:%m}", MG_ESC("error"),
|
||||
MG_ESC("can't create response"));
|
||||
free(filename);
|
||||
free(res);
|
||||
return;
|
||||
}
|
||||
if (!cJSON_AddStringToObject(response, "data", res)) {
|
||||
mg_http_reply(c, 500, HTTP_HEADERS, "{%m:%m}", MG_ESC("error"),
|
||||
MG_ESC("can't add data to response"));
|
||||
cJSON_Delete(response);
|
||||
free(filename);
|
||||
free(res);
|
||||
return;
|
||||
}
|
||||
char *response_str = cJSON_PrintUnformatted(response);
|
||||
mg_http_reply(
|
||||
c, 200, "Content-Type: text/plain\r\nAccess-Control-Allow-Origin: *\r\n",
|
||||
response_str);
|
||||
cJSON_Delete(response);
|
||||
free(response_str);
|
||||
free(filename);
|
||||
free(res);
|
||||
}
|
||||
|
||||
@@ -11,3 +11,6 @@ void set_settings(struct mg_connection *c, struct mg_http_message *hm);
|
||||
cJSON *decode_dnsmasq_settings(char *input, char *pair_delim, char *kv_delim);
|
||||
char **decode_resolvs(char *input, int *count);
|
||||
cJSON *decode_hosts(const char *input);
|
||||
char *set_hosts(cJSON* hosts);
|
||||
char *set_resolvs(cJSON* resolvs);
|
||||
char *set_dnsmasq_settings_field(const char *key, cJSON *setting);
|
||||
|
||||
+124
-118
@@ -1,207 +1,213 @@
|
||||
const upstream_dns_servers_table_body = document.querySelector(
|
||||
"#upstream-dns-servers-table-body",
|
||||
"#upstream-dns-servers-table-body",
|
||||
);
|
||||
const local_domains_table_body = document.querySelector(
|
||||
"#local-domains-table-body",
|
||||
"#local-domains-table-body",
|
||||
);
|
||||
const cache_size_input = document.querySelector("#cache-size-input");
|
||||
const save_settings_button = document.querySelector("#save-settings-button");
|
||||
const add_dns_server_button = document.querySelector("#add_dns_server_button");
|
||||
const add_local_domain_button = document.querySelector(
|
||||
"#add_local_domain_button",
|
||||
"#add_local_domain_button",
|
||||
);
|
||||
const edit_file_input = document.querySelector("#edit_file_input");
|
||||
const save_config_file_button = document.querySelector(
|
||||
"#save_config_file_button",
|
||||
"#save_config_file_button",
|
||||
);
|
||||
|
||||
var settings = {};
|
||||
var configFile = "";
|
||||
|
||||
const updateDnsServersTable = () => {
|
||||
upstream_dns_servers_table_body.innerHTML = "";
|
||||
for (var i = 0; i < settings.resolv.length; i++) {
|
||||
upstream_dns_servers_table_body.innerHTML += `<tr>
|
||||
upstream_dns_servers_table_body.innerHTML = "";
|
||||
for (var i = 0; i < settings.resolv.length; i++) {
|
||||
upstream_dns_servers_table_body.innerHTML += `<tr>
|
||||
<th class="d-flex align-items-center justify-content-between">
|
||||
${settings.resolv[i]}
|
||||
<button id="${i}" class="delete-table-button btn btn-danger">Del</button>
|
||||
</th>
|
||||
</tr>`;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const updateLocalDomainsTable = () => {
|
||||
local_domains_table_body.innerHTML = "";
|
||||
for (const [key, value] of Object.entries(settings.hosts)) {
|
||||
if (Array.isArray(value)) {
|
||||
for (var i = 0; i < value.length; i++) {
|
||||
local_domains_table_body.innerHTML += `<tr>
|
||||
local_domains_table_body.innerHTML = "";
|
||||
for (const [key, value] of Object.entries(settings.hosts)) {
|
||||
if (Array.isArray(value)) {
|
||||
for (var i = 0; i < value.length; i++) {
|
||||
local_domains_table_body.innerHTML += `<tr>
|
||||
<th>${key}</th>
|
||||
<th>${value[i]}</th>
|
||||
<th><button class="deldomain btn btn-danger" id="${key}|${value[i]}">Del</button>
|
||||
</th>
|
||||
</tr>`;
|
||||
}
|
||||
} else {
|
||||
local_domains_table_body.innerHTML += `<tr>
|
||||
}
|
||||
} else {
|
||||
local_domains_table_body.innerHTML += `<tr>
|
||||
<th>${key}</th>
|
||||
<th>${value}</th>
|
||||
<th><button class="deldomain btn btn-danger" id="${key}|${value}">Del</button>
|
||||
</th>
|
||||
</tr>`;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const updateInterface = () => {
|
||||
updateDnsServersTable();
|
||||
updateLocalDomainsTable();
|
||||
cache_size_input.value = settings.cache_size;
|
||||
updateDnsServersTable();
|
||||
updateLocalDomainsTable();
|
||||
cache_size_input.value = settings.cache_size;
|
||||
};
|
||||
|
||||
const getSettings = () => {
|
||||
fetch(window.location.origin + "/api/settings")
|
||||
.then((data) => data.json())
|
||||
.then((jsondata) => {
|
||||
settings = jsondata;
|
||||
updateInterface();
|
||||
})
|
||||
.catch((e) => alert(e));
|
||||
fetch(window.location.origin + "/api/settings")
|
||||
.then((data) => data.json())
|
||||
.then((jsondata) => {
|
||||
settings = jsondata;
|
||||
updateInterface();
|
||||
})
|
||||
.catch((e) => alert(e));
|
||||
};
|
||||
|
||||
const setSettings = () => {
|
||||
fetch(window.location.origin + "/api/settings", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(settings),
|
||||
fetch(window.location.origin + "/api/settings", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(settings),
|
||||
})
|
||||
.then((data) => data.json())
|
||||
.then((jsondata) => {
|
||||
if (jsondata.error) {
|
||||
alert(jsondata.error);
|
||||
return;
|
||||
}
|
||||
settings = jsondata;
|
||||
updateInterface();
|
||||
alert("Success set settings");
|
||||
})
|
||||
.then((data) => data.json())
|
||||
.then((jsondata) => {
|
||||
settings = jsondata;
|
||||
updateInterface();
|
||||
alert("Success set settings");
|
||||
})
|
||||
.catch((e) => alert(e));
|
||||
.catch((e) => alert(e));
|
||||
};
|
||||
|
||||
save_settings_button.addEventListener("click", () => {
|
||||
setSettings();
|
||||
setSettings();
|
||||
});
|
||||
|
||||
const write_cache_size = (value) => {
|
||||
settings.cache_size = value;
|
||||
settings.cache_size = parseInt(value);
|
||||
};
|
||||
|
||||
cache_size_input.addEventListener("change", (e) => {
|
||||
write_cache_size(e.target.value);
|
||||
write_cache_size(e.target.value);
|
||||
});
|
||||
|
||||
cache_size_input.addEventListener("input", (e) => {
|
||||
write_cache_size(e.target.value);
|
||||
write_cache_size(e.target.value);
|
||||
});
|
||||
|
||||
getSettings();
|
||||
|
||||
add_dns_server_button.addEventListener("click", () => {
|
||||
const dns_server = prompt("Input your DNS server:");
|
||||
if (dns_server === null) return;
|
||||
if (confirm("Confirm DNS settings? (add DNS server)")) {
|
||||
if (Array.isArray(settings.resolv)) {
|
||||
if (!settings.resolv.includes(dns_server)) {
|
||||
settings.resolv.push(dns_server);
|
||||
updateDnsServersTable();
|
||||
}
|
||||
}
|
||||
const dns_server = prompt("Input your DNS server:");
|
||||
if (dns_server === null) return;
|
||||
if (confirm("Confirm DNS settings? (add DNS server)")) {
|
||||
if (Array.isArray(settings.resolv)) {
|
||||
if (!settings.resolv.includes(dns_server)) {
|
||||
settings.resolv.push(dns_server);
|
||||
updateDnsServersTable();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
upstream_dns_servers_table_body.addEventListener("click", (e) => {
|
||||
if (e.target.classList.contains("delete-table-button")) {
|
||||
if (
|
||||
confirm(`Confirm DNS settings? (delete DNS server: ${e.target.id})`)
|
||||
) {
|
||||
if (Array.isArray(settings.resolv)) {
|
||||
settings.resolv.splice(e.target.id, 1);
|
||||
updateDnsServersTable();
|
||||
}
|
||||
}
|
||||
if (e.target.classList.contains("delete-table-button")) {
|
||||
if (confirm(`Confirm DNS settings? (delete DNS server: ${e.target.id})`)) {
|
||||
if (Array.isArray(settings.resolv)) {
|
||||
settings.resolv.splice(e.target.id, 1);
|
||||
updateDnsServersTable();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
add_local_domain_button.addEventListener("click", () => {
|
||||
const domainip = prompt("Input localdomain IP:");
|
||||
if (domainip === null) return;
|
||||
const domain = prompt("Input domain:");
|
||||
if (domainip === null || domain === null) return;
|
||||
if (
|
||||
confirm(
|
||||
`Confirm DNS settings? (Add Local Domain: ${domainip} ${domain})`,
|
||||
)
|
||||
) {
|
||||
for (const [key, value] of Object.entries(settings.hosts)) {
|
||||
if (key === domainip) {
|
||||
if (Array.isArray(value)) {
|
||||
if (!value.includes(domain)) {
|
||||
value.push(domain);
|
||||
updateLocalDomainsTable();
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
const domainsarray = [value, domain];
|
||||
settings.hosts[key] = domainsarray;
|
||||
updateLocalDomainsTable();
|
||||
return;
|
||||
}
|
||||
const domainip = prompt("Input localdomain IP:");
|
||||
if (domainip === null) return;
|
||||
const domain = prompt("Input domain:");
|
||||
if (domainip === null || domain === null) return;
|
||||
if (
|
||||
confirm(`Confirm DNS settings? (Add Local Domain: ${domainip} ${domain})`)
|
||||
) {
|
||||
for (const [key, value] of Object.entries(settings.hosts)) {
|
||||
if (key === domainip) {
|
||||
if (Array.isArray(value)) {
|
||||
if (!value.includes(domain)) {
|
||||
value.push(domain);
|
||||
updateLocalDomainsTable();
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
settings.hosts[domainip] = domain;
|
||||
const domainsarray = [value, domain];
|
||||
settings.hosts[key] = domainsarray;
|
||||
updateLocalDomainsTable();
|
||||
return;
|
||||
}
|
||||
}
|
||||
settings.hosts[domainip] = domain;
|
||||
updateLocalDomainsTable();
|
||||
}
|
||||
});
|
||||
|
||||
local_domains_table_body.addEventListener("click", (e) => {
|
||||
if (e.target.classList.contains("deldomain")) {
|
||||
if (confirm(`Confirm DNS settings? (delete domain: ${e.target.id})`)) {
|
||||
const domainip = e.target.id.split("|")[0];
|
||||
const domain = e.target.id.split("|")[1];
|
||||
for (const [key, value] of Object.entries(settings.hosts)) {
|
||||
if (key === domainip) {
|
||||
if (Array.isArray(value)) {
|
||||
if (value.includes(domain)) {
|
||||
value.splice(value.indexOf(domain), 1);
|
||||
updateLocalDomainsTable();
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
delete settings.hosts[key];
|
||||
updateLocalDomainsTable();
|
||||
return;
|
||||
}
|
||||
if (e.target.classList.contains("deldomain")) {
|
||||
if (confirm(`Confirm DNS settings? (delete domain: ${e.target.id})`)) {
|
||||
const domainip = e.target.id.split("|")[0];
|
||||
const domain = e.target.id.split("|")[1];
|
||||
for (const [key, value] of Object.entries(settings.hosts)) {
|
||||
if (key === domainip) {
|
||||
if (Array.isArray(value)) {
|
||||
if (value.includes(domain)) {
|
||||
value.splice(value.indexOf(domain), 1);
|
||||
updateLocalDomainsTable();
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
delete settings.hosts[key];
|
||||
updateLocalDomainsTable();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const getConfigFile = () => {
|
||||
fetch(window.location.origin + "/api/settings/file")
|
||||
.then((data) => data.text())
|
||||
.then((textdata) => (edit_file_input.value = textdata))
|
||||
.catch((e) => alert(e));
|
||||
fetch(window.location.origin + "/api/settings/file")
|
||||
.then((data) => data.text())
|
||||
.then((textdata) => (edit_file_input.value = textdata))
|
||||
.catch((e) => alert(e));
|
||||
};
|
||||
|
||||
getConfigFile();
|
||||
|
||||
save_config_file_button.addEventListener("click", () => {
|
||||
if (confirm("Save Config file?")) {
|
||||
fetch(window.location.origin + "/api/settings/file", {
|
||||
method: "POST",
|
||||
body: edit_file_input.value,
|
||||
})
|
||||
.then((data) => data.text())
|
||||
.then((textdata) => {
|
||||
edit_file_input.value = textdata;
|
||||
alert("Success write settings file");
|
||||
})
|
||||
.catch((e) => alert(e));
|
||||
save_config_file_button.addEventListener("click", async () => {
|
||||
if (confirm("Save Config file?")) {
|
||||
var response = await fetch(window.location.origin + "/api/settings/file", {
|
||||
method: "POST",
|
||||
body: edit_file_input.value,
|
||||
});
|
||||
if (!response.ok) {
|
||||
const result = await response.json();
|
||||
alert(result.error || "Failed to save config file");
|
||||
return;
|
||||
}
|
||||
const result = await response.json();
|
||||
if (result.error) {
|
||||
alert(result.error);
|
||||
} else {
|
||||
edit_file_input.value = result.data;
|
||||
alert("Success write settings file");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user