aboutsummaryrefslogtreecommitdiff
path: root/src/main.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.cpp')
-rw-r--r--src/main.cpp327
1 files changed, 276 insertions, 51 deletions
diff --git a/src/main.cpp b/src/main.cpp
index 19b5ced..b8c8ec6 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,5 +1,14 @@
+#include <iostream>
+#include <string>
+#include <vector>
+#include <filesystem>
+#include <cstdlib>
+#include <cstring>
+#include <cstdio>
+#include <getopt.h>
+
// =====================================
-// ERROR HANDING - FROM FCUTILS
+// ERROR HANDING - FROM FCUTILS
// =====================================
#include <stdio.h>
@@ -69,35 +78,122 @@ void err(char const * format, ...) {
}
// =============================================
-// COMMAND LINE UTILITY
+// COMMAND LINE UTILITY
// =============================================
+constexpr char const * DEFAULT_OUTPUT_PATH = "/etc/systemd/network/";
-#include "wg2sd.hpp"
+/*
+ * HELP AND USAGE
+ */
-#include <filesystem>
-#include <fstream>
-#include <iostream>
-#include <unistd.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <grp.h>
+void die_usage(const char *prog) {
+ err("Usage: %s { install, generate } [ OPTIONS ] { -h, CONFIG_FILE }", prog);
+ die("Use -h for help");
+}
+
+void print_help(const char *prog) {
+ err("Usage: %s { install, generate } [ OPTIONS ] { -h, CONFIG_FILE }\n", prog);
+ err(" CONFIG_FILE is the complete path to a WireGuard configuration file, used by");
+ err(" `wg-quick`. `wg2nd` will convert the WireGuard configuration to networkd");
+ err(" files.\n");
+ err(" The generated configurations are functionally equivalent to `wg-quick(8)`");
+ err(" with the following exceptions:\n");
+ err(" 1. When unspecified, `wg-quick` determines whether `FwMark` and `Table` are available dynamically,");
+ err(" ensuring that the routing table and `fwmark` are not already in use. `wg2nd` sets");
+ err(" the `fwmark` to a random number (deterministically generated from the interface");
+ err(" name). If more than 500 `fwmarks` are in use, there is a non-negligible chance of a");
+ err(" collision. This would occur when there are more than 500 active WireGuard interfaces.\n");
+ err(" 2. The PreUp, PostUp, PreDown, and PostDown script snippets are ignored.\n");
+ err(" 3. `wg-quick(8)` installs a firewall when a default route is specified (i.e., when `0.0.0.0/0`");
+ err(" or `::/0` are specified in `AllowedIPs`). This is not installed by");
+ err(" default with `wg2nd install`. The equivalent firewall can be generated with");
+ err(" `wg2nd generate -t nft CONFIG_FILE`. Refer to `nft(8)` for details.\n");
+ err(" Actions:");
+ err(" install Generate and install the configuration with restricted permissions");
+ err(" generate Generate specific configuration files and write the results to stdout\n");
+ err(" Options:");
+ err(" -h Print this help");
+ exit(EXIT_SUCCESS);
+}
-[[noreturn]] void die_usage(char const * prog) {
- err("Usage: %s [ -o OUTPUT_PATH ] CONFIG_FILE", prog);
+void die_usage_generate(const char *prog) {
+ err("Usage: %s generate [ -h ] [ -k KEYPATH ] [ -t { network, netdev, keyfile, nft } ] CONFIG_FILE", prog);
die("Use -h for help");
}
-void print_help(char const * prog) {
- err("Usage: %s [ -h | -f | -o OUTPUT_PATH ] CONFIG_FILE", prog);
+void print_help_generate(const char *prog) {
+ err("Usage: %s generate [ -h ] [ -k KEYPATH ] [ -t { network, netdev, keyfile, nft } ] CONFIG_FILE\n", prog);
err("Options:");
- err("-o OUTPUT_PATH\tSet the output path (default is /etc/systemd/network)");
- err("-f \tOutput firewall rules");
- err("-h \tDisplay this help message");
+ err(" -t FILE_TYPE");
+ err(" network Generate a Network Configuration File (see systemd.network(8))");
+ err(" netdev Generate a Virtual Device File (see systemd.netdev(8))");
+ err(" keyfile Print the interface's private key");
+ err(" nft Print the netfilter table `nft(8)` installed by `wg-quick(8)`\n");
+ err(" -k KEYPATH Full path to the keyfile (a path relative to /etc/systemd/network is generated");
+ err(" if unspecified)\n");
+ err(" -h Print this help");
exit(EXIT_SUCCESS);
}
-using namespace wg2sd;
+void die_usage_install(const char *prog) {
+ err("Usage: %s install [ -h ] [ -f FILE_NAME ] [ -k KEYFILE ] [ -o OUTPUT_PATH ] CONFIG_FILE", prog);
+ die("Use -h for help");
+}
+
+void print_help_install(const char *prog) {
+ err("Usage: %s install [ -h ] [ -f FILE_NAME ] [ -o OUTPUT_PATH ] CONFIG_FILE\n", prog);
+ err(" `wg2nd install` translates `wg-quick(8)` configuration into corresponding");
+ err(" `networkd` configuration and installs the resulting files in `OUTPUT_PATH`.\n");
+ err(" `wg2nd install` generates a `netdev`, `network`, and `keyfile` for each");
+ err(" CONFIG_FILE. Links will be installed with a `manual` `ActivationPolicy`.");
+ err(" The interface can be brought up with `networkctl up INTERFACE` and down");
+ err(" with `networkctl down INTERFACE`.\n");
+ err(" `wg-quick(8)` installs a firewall when a default route (i.e., when `0.0.0.0/0`");
+ err(" or `::/0` is specified in `AllowedIPs`). This is not installed by default");
+ err(" with `wg2nd install`. The equivalent firewall can be generated with");
+ err(" `wg2nd generate -t nft CONFIG_FILE`.\n");
+ err("Options:");
+ err(" -o OUTPUT_PATH The installation path (default is /etc/systemd/network)\n");
+ err(" -f FILE_NAME The base name for the installed configuration files. The");
+ err(" networkd-specific configuration suffix will be added");
+ err(" (FILE_NAME.netdev for systemd-netdev(8) files,");
+ err(" FILE_NAME.network for systemd-network(8) files,");
+ err(" and FILE_NAME.keyfile for keyfiles)\n");
+ err(" -k KEYFILE The name of the private keyfile\n");
+ err(" -h Print this help");
+ exit(EXIT_SUCCESS);
+}
+
+
+/*
+ * PARSING
+ */
+
+enum class FileType {
+ NONE = 0,
+ NETWORK,
+ NETDEV,
+ KEYFILE,
+ NFT
+};
+
+
+/*
+ * INTERNAL LOGIC
+ */
+
+#include "wg2nd.hpp"
+
+#include <iostream>
+#include <fstream>
+#include <optional>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <grp.h>
+
+using namespace wg2nd;
void write_systemd_file(SystemdFilespec const & filespec, std::string output_path, bool secure) {
std::string full_path = output_path + "/" + filespec.name;
@@ -142,44 +238,23 @@ void write_systemd_file(SystemdFilespec const & filespec, std::string output_pat
}
}
-int main(int argc, char ** argv) {
- int opt;
- std::filesystem::path output_path = "/etc/systemd/network";
- bool print_firewall_rules = false;
-
- while ((opt = getopt(argc, argv, "o:fh")) != -1) {
- switch (opt) {
- case 'o':
- output_path = optarg;
- break;
- case 'f':
- print_firewall_rules = true;
- break;
- case 'h':
- print_help(argv[0]);
- break;
- default: /* '?' */
- die_usage(argv[0]);
- }
- }
-
- if (optind >= argc) {
- die_usage(argv[0]);
- }
-
- std::filesystem::path config_path = argv[optind];
+SystemdConfig generate_cfg_or_die(
+ std::filesystem::path && config_path,
+ std::filesystem::path const & keyfile_or_output_path,
+ std::optional<std::string> const & filename
+ ) {
std::fstream cfg_stream { config_path };
if(!cfg_stream.is_open()) {
die("Failed to open config file %s", config_path.string().c_str());
}
- std::string interface_name = interface_name_from_filename(config_path);
-
SystemdConfig cfg;
+ std::string interface_name = interface_name_from_filename(config_path);
+
try {
- cfg = wg2sd::wg2sd(interface_name, cfg_stream, output_path);
+ cfg = wg2nd::wg2nd(interface_name, cfg_stream, keyfile_or_output_path, filename);
} catch(ConfigurationException const & cex) {
const ParsingException * pex = dynamic_cast<const ParsingException *>(&cex);
@@ -191,16 +266,25 @@ int main(int argc, char ** argv) {
}
- if(print_firewall_rules) {
- fprintf(stdout, "%s", cfg.firewall.c_str());
+ return cfg;
+}
- return 0;
- }
+
+void wg2nd_install_internal(std::optional<std::string> && filename, std::string && keyfile_name,
+ std::filesystem::path && output_path, std::filesystem::path && config_path) {
if(!std::filesystem::path(output_path).is_absolute()) {
output_path = std::filesystem::absolute(output_path);
}
+ std::filesystem::path keyfile_or_output_path = output_path;
+
+ if(!keyfile_name.empty()) {
+ keyfile_or_output_path /= keyfile_name;
+ }
+
+ SystemdConfig cfg = generate_cfg_or_die(std::move(config_path), keyfile_or_output_path, std::move(filename));
+
for(std::string const & warning : cfg.warnings) {
err("warning: %s", warning.c_str());
}
@@ -212,6 +296,147 @@ int main(int argc, char ** argv) {
for(SystemdFilespec const & spec : cfg.symmetric_keyfiles) {
write_systemd_file(spec, output_path, true);
}
+}
+
+void wg2nd_generate_internal(FileType type, std::string && config_file,
+ std::optional<std::filesystem::path> && keyfile_path) {
+
+ SystemdConfig cfg = generate_cfg_or_die(
+ std::move(config_file), std::move(keyfile_path.value_or(DEFAULT_OUTPUT_PATH)), {}
+ );
+
+ switch(type) {
+ case FileType::NFT:
+ printf("%s", cfg.firewall.c_str());
+ break;
+ case FileType::NETWORK:
+ printf("%s", cfg.network.contents.c_str());
+ break;
+ case FileType::NETDEV:
+ printf("%s", cfg.netdev.contents.c_str());
+ break;
+ case FileType::KEYFILE:
+ printf("%s", cfg.private_keyfile.contents.c_str());
+ break;
+ default:
+ break;
+ }
+}
+
+
+static int wg2nd_generate(char const * prog, int argc, char **argv) {
+ std::filesystem::path config_path = "";
+
+ FileType type = FileType::NONE;
+ std::optional<std::filesystem::path> keyfile_path = {};
+
+ int opt;
+ while ((opt = getopt(argc, argv, "ht:k:")) != -1) {
+ switch (opt) {
+ case 't':
+ if (strcmp(optarg, "network") == 0) {
+ type = FileType::NETWORK;
+ } else if (strcmp(optarg, "netdev") == 0) {
+ type = FileType::NETDEV;
+ } else if (strcmp(optarg, "keyfile") == 0) {
+ type = FileType::KEYFILE;
+ } else if (strcmp(optarg, "nft") == 0) {
+ type = FileType::NFT;
+ } else {
+ die("Unknown file type: %s", optarg);
+ }
+ break;
+ case 'k':
+ keyfile_path = optarg;
+ break;
+ case 'h':
+ print_help_generate(prog);
+ break;
+ default:
+ die_usage_generate(prog);
+ }
+ }
+
+ if (optind >= argc) {
+ die_usage_generate(prog);
+ }
+
+ config_path = argv[optind];
+
+ wg2nd_generate_internal(type, std::move(config_path), std::move(keyfile_path));
+
+ return 0;
+}
+
+static int wg2nd_install(char const * prog, int argc, char **argv) {
+ std::filesystem::path config_path = "";
+
+ std::optional<std::string> filename = {};
+ std::filesystem::path output_path = DEFAULT_OUTPUT_PATH;
+ std::string keyfile_name = "";
+
+ int opt;
+ while ((opt = getopt(argc, argv, "o:f:k:h")) != -1) {
+ switch (opt) {
+ case 'o': {
+ std::string path = optarg;
+ if(path[path.size() - 1] != '/') {
+ path.push_back('/');
+ }
+ output_path = std::move(path);
+ break;
+ }
+ case 'f':
+ filename = optarg;
+ break;
+ case 'h':
+ print_help_install(prog);
+ break;
+ case 'k':
+ keyfile_name = optarg;
+ break;
+ default:
+ die_usage_install(prog);
+ }
+ }
+
+ if (optind >= argc) {
+ die_usage_install(prog);
+ }
+
+ config_path = argv[optind];
+
+ wg2nd_install_internal(std::move(filename), std::move(keyfile_name), std::move(output_path), std::move(config_path));
+
+ return 0;
+}
+
+// The main function remains the same as before
+
+
+int main(int argc, char **argv) {
+ char const * prog = "wg2nd";
+
+ if(argc > 0) {
+ prog = argv[0];
+ }
+
+ if (argc < 2) {
+ die_usage(prog);
+ }
+
+ std::string action = argv[1];
+
+ if (action == "generate") {
+ return wg2nd_generate(prog, argc - 1, argv + 1);
+ } else if (action == "install") {
+ return wg2nd_install(prog, argc - 1, argv + 1);
+ } else if (action == "-h" || action == "--help") {
+ print_help(prog);
+ } else {
+ err("Unknown action: %s", action.c_str());
+ die_usage(prog);
+ }
return 0;
}