summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRoland Reichwein <mail@reichwein.it>2020-04-15 20:01:25 +0200
committerRoland Reichwein <mail@reichwein.it>2020-04-15 20:01:25 +0200
commitf6e703a938a95c555b388f79966cf955c5d07dc6 (patch)
treef47eae93adf9f72b1472ced2a1a051e90845dafe
parent1ce0bb7ad50129fbab6c0a75f18eee6149ca1be3 (diff)
HTTP Auth (Basic)
-rw-r--r--Makefile1
-rw-r--r--base64.cpp23
-rw-r--r--base64.h7
-rw-r--r--config.cpp15
-rw-r--r--config.h1
-rw-r--r--response.cpp28
-rw-r--r--webserver.conf1
7 files changed, 74 insertions, 2 deletions
diff --git a/Makefile b/Makefile
index 84b05ec..c7834d0 100644
--- a/Makefile
+++ b/Makefile
@@ -58,6 +58,7 @@ LIBS+= \
endif
PROGSRC=\
+ base64.cpp \
config.cpp \
file.cpp \
http.cpp \
diff --git a/base64.cpp b/base64.cpp
new file mode 100644
index 0000000..3847f0a
--- /dev/null
+++ b/base64.cpp
@@ -0,0 +1,23 @@
+#include "base64.h"
+
+#include <boost/archive/iterators/binary_from_base64.hpp>
+#include <boost/archive/iterators/base64_from_binary.hpp>
+#include <boost/archive/iterators/transform_width.hpp>
+#include <boost/algorithm/string.hpp>
+
+std::string decode64(const std::string &val)
+{
+ using namespace boost::archive::iterators;
+ using It = transform_width<binary_from_base64<std::string::const_iterator>, 8, 6>;
+ return boost::algorithm::trim_right_copy_if(std::string(It(std::begin(val)), It(std::end(val))), [](char c) {
+ return c == '\0';
+ });
+}
+
+std::string encode64(const std::string &val)
+{
+ using namespace boost::archive::iterators;
+ using It = base64_from_binary<transform_width<std::string::const_iterator, 6, 8>>;
+ auto tmp = std::string(It(std::begin(val)), It(std::end(val)));
+ return tmp.append((3 - val.size() % 3) % 3, '=');
+}
diff --git a/base64.h b/base64.h
new file mode 100644
index 0000000..6a3f2c3
--- /dev/null
+++ b/base64.h
@@ -0,0 +1,7 @@
+#pragma once
+
+#include <string>
+
+std::string decode64(const std::string &val);
+std::string encode64(const std::string &val);
+
diff --git a/config.cpp b/config.cpp
index 0d9c117..3750bcf 100644
--- a/config.cpp
+++ b/config.cpp
@@ -63,8 +63,19 @@ void Config::readConfigfile(std::string filename)
auto attrs = x.second.get_child("<xmlattr>");
path.requested = attrs.get<std::string>("requested");
for (const auto& param: x.second) { // get all sub-elements of <path>
- if (param.first.size() > 0 && param.first[0] != '<') // exclude meta-elements like <xmlattr>
- path.params[param.first.data()] = param.second.data();
+ if (param.first.size() > 0 && param.first[0] != '<') { // exclude meta-elements like <xmlattr>
+ if (param.first == "auth") {
+ try {
+ std::string login{param.second.get<std::string>("<xmlattr>.login")};
+ std::string password{param.second.get<std::string>("<xmlattr>.password")};
+ path.auth[login] = password;
+ } catch (const std::exception& ex) {
+ std::cerr << "Warning: Can't read auth data from config: " << ex.what() << std::endl;
+ }
+ } else {
+ path.params[param.first] = param.second.data();
+ }
+ }
}
site_struct.paths.push_back(path);
} else if (x.first == "certpath"s) {
diff --git a/config.h b/config.h
index 746b95c..46cd267 100644
--- a/config.h
+++ b/config.h
@@ -13,6 +13,7 @@ struct Path
std::string requested; // the requested path
// mandatory entries: "plugin", "target", others are optional
std::unordered_map<std::string, std::string> params; // what to serve, e.g. which filesystem path (target), and which plugin
+ std::unordered_map<std::string, std::string> auth; // optional
};
struct Site
diff --git a/response.cpp b/response.cpp
index 8f66c54..0c619a2 100644
--- a/response.cpp
+++ b/response.cpp
@@ -1,4 +1,6 @@
#include "response.h"
+
+#include "base64.h"
#include "file.h"
#include <boost/algorithm/string/predicate.hpp>
@@ -183,6 +185,8 @@ response_type HttpStatus(std::string status, std::string message, response_type&
{
res.result(unsigned(stoul(status)));
res.set(http::field::content_type, "text/html");
+ if (res.result_int() == 401)
+ res.set(http::field::www_authenticate, "Basic realm=\"Webbox Login\"");
res.body() = "<html><body><h1>"s + VersionString + " Error</h1><p>"s + status + " "s + message + "</p></body></html>"s;
res.prepare_payload();
@@ -201,6 +205,30 @@ response_type generate_response(request_type& req, Server& server)
try {
RequestContext req_ctx{req, server}; // can throw std::out_of_range
+ auto& auth{req_ctx.GetPath().auth};
+ if (auth.size() != 0) {
+ std::string authorization{req[http::field::authorization]};
+ if (authorization.substr(0, 6) != "Basic "s)
+ return HttpStatus("401", "Bad Authorization Type", res);
+
+ authorization = authorization.substr(6);
+ authorization = decode64(authorization);
+
+ size_t pos {authorization.find(':')};
+ if (pos == authorization.npos)
+ return HttpStatus("401", "Bad Authorization Encoding", res);
+
+ std::string login{authorization.substr(0, pos)};
+ std::string password{authorization.substr(pos + 1)};
+
+ auto it {auth.find(login)};
+ if (it == auth.end())
+ return HttpStatus("401", "Bad Authorization", res);
+
+ if (it->second != password)
+ return HttpStatus("401", "Bad Authorization", res); // should be same message as previous one to prevent login guessing
+ }
+
plugin_type plugin{req_ctx.GetPlugin()};
auto GetServerParamFunction {std::function<std::string(const std::string& key)>(std::bind(GetServerParam, _1, std::ref(server)))};
diff --git a/webserver.conf b/webserver.conf
index 35ff2f6..fb5def6 100644
--- a/webserver.conf
+++ b/webserver.conf
@@ -30,6 +30,7 @@
<target>/home/ernie/testbox</target>
<WEBBOX_NAME>Testbox1</WEBBOX_NAME>
<WEBBOX_READONLY>0</WEBBOX_READONLY>
+ <auth login="abc" password="def"/>
</path>
<certpath>/home/ernie/code/webserver/fullchain.pem</certpath>
<keypath>/home/ernie/code/webserver/privkey.pem</keypath>