1 /*
2  * This file is part of PowerDNS or dnsdist.
3  * Copyright -- PowerDNS.COM B.V. and its contributors
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of version 2 of the GNU General Public License as
7  * published by the Free Software Foundation.
8  *
9  * In addition, for the avoidance of any doubt, permission is granted to
10  * link this program with OpenSSL and to (re)distribute the binaries
11  * produced as the result of such linking.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21  */
22 #pragma once
23 #include <map>
24 #include <string>
25 #include <list>
26 #include <boost/utility.hpp>
27 #include <yahttp/yahttp.hpp>
28 #include "json11.hpp"
29 #include "namespaces.hh"
30 #include "sstuff.hh"
31 
32 class HttpRequest : public YaHTTP::Request {
33 public:
HttpRequest(const string & logprefix_="")34   HttpRequest(const string& logprefix_="") : YaHTTP::Request(), logprefix(logprefix_) { };
35 
36   string logprefix;
37   bool accept_yaml{false};
38   bool accept_json{false};
39   bool accept_html{false};
40   bool complete{false};
41 
42   json11::Json json();
43 
44   // checks password _only_.
45   bool compareAuthorization(const string &expected_password);
46   bool compareHeader(const string &header_name, const string &expected_value);
47 };
48 
49 class HttpResponse: public YaHTTP::Response {
50 public:
HttpResponse()51   HttpResponse() : YaHTTP::Response() { };
HttpResponse(const YaHTTP::Response & resp)52   HttpResponse(const YaHTTP::Response &resp) : YaHTTP::Response(resp) { };
53 
54   void setPlainBody(const string& document);
55   void setYamlBody(const string& document);
56   void setJsonBody(const string& document);
57   void setJsonBody(const json11::Json& document);
58   void setErrorResult(const std::string& message, const int status);
59   void setSuccessResult(const std::string& message, const int status = 200);
60 };
61 
62 
63 class HttpException
64 {
65 public:
HttpException(int status)66   HttpException(int status) : d_response()
67   {
68     d_response.status = status;
69   };
70 
HttpException(int status,const string & msg)71   HttpException(int status, const string& msg) : d_response()
72   {
73     d_response.setErrorResult(msg, status);
74   };
75 
response()76   HttpResponse response()
77   {
78     return d_response;
79   }
80 
81 protected:
82   HttpResponse d_response;
83 };
84 
85 class HttpBadRequestException : public HttpException {
86 public:
HttpBadRequestException()87   HttpBadRequestException() : HttpException(400) { };
HttpBadRequestException(const string & msg)88   HttpBadRequestException(const string& msg) : HttpException(400, msg) { };
89 };
90 
91 class HttpUnauthorizedException : public HttpException {
92 public:
HttpUnauthorizedException(string const & scheme)93   HttpUnauthorizedException(string const &scheme) : HttpException(401)
94   {
95     d_response.headers["WWW-Authenticate"] = scheme + " realm=\"PowerDNS\"";
96   }
97 };
98 
99 class HttpForbiddenException : public HttpException {
100 public:
HttpForbiddenException()101   HttpForbiddenException() : HttpException(403) { };
HttpForbiddenException(const string & msg)102   HttpForbiddenException(const string& msg) : HttpException(403, msg) { };
103 };
104 
105 class HttpNotFoundException : public HttpException {
106 public:
HttpNotFoundException()107   HttpNotFoundException() : HttpException(404) { };
HttpNotFoundException(const string & msg)108   HttpNotFoundException(const string& msg) : HttpException(404, msg) { };
109 };
110 
111 class HttpMethodNotAllowedException : public HttpException {
112 public:
HttpMethodNotAllowedException()113   HttpMethodNotAllowedException() : HttpException(405) { };
HttpMethodNotAllowedException(const string & msg)114   HttpMethodNotAllowedException(const string& msg) : HttpException(405, msg) { };
115 };
116 
117 class HttpConflictException : public HttpException {
118 public:
HttpConflictException()119   HttpConflictException() : HttpException(409) { };
HttpConflictException(const string & msg)120   HttpConflictException(const string& msg) : HttpException(409, msg) { };
121 };
122 
123 class HttpInternalServerErrorException : public HttpException {
124 public:
HttpInternalServerErrorException()125   HttpInternalServerErrorException() : HttpException(500) { };
HttpInternalServerErrorException(const string & msg)126   HttpInternalServerErrorException(const string& msg) : HttpException(500, msg) { };
127 };
128 
129 class ApiException : public runtime_error
130 {
131 public:
ApiException(const string & what_arg)132   ApiException(const string& what_arg) : runtime_error(what_arg) {
133   }
134 };
135 
136 class Server
137 {
138 public:
Server(const string & localaddress,int port)139   Server(const string &localaddress, int port) : d_local(localaddress.empty() ? "0.0.0.0" : localaddress, port), d_server_socket(d_local.sin4.sin_family, SOCK_STREAM, 0) {
140     d_server_socket.setReuseAddr();
141     d_server_socket.bind(d_local);
142     d_server_socket.listen();
143   }
~Server()144   virtual ~Server() { };
145 
146   ComboAddress d_local;
147 
accept()148   std::shared_ptr<Socket> accept() {
149     return std::shared_ptr<Socket>(d_server_socket.accept());
150   }
151 
152 protected:
153   Socket d_server_socket;
154 };
155 
156 class WebServer : public boost::noncopyable
157 {
158 public:
159   WebServer(string listenaddress, int port);
~WebServer()160   virtual ~WebServer() { };
161 
setApiKey(const string & apikey)162   void setApiKey(const string &apikey) {
163     d_apikey = apikey;
164   }
165 
setPassword(const string & password)166   void setPassword(const string &password) {
167     d_webserverPassword = password;
168   }
169 
setMaxBodySize(ssize_t s)170   void setMaxBodySize(ssize_t s) { // in megabytes
171     d_maxbodysize = s * 1024 * 1024;
172   }
173 
setACL(const NetmaskGroup & nmg)174   void setACL(const NetmaskGroup &nmg) {
175     d_acl = nmg;
176   }
177 
178   void bind();
179   void go();
180 
181   void serveConnection(const std::shared_ptr<Socket>& client) const;
182   void handleRequest(HttpRequest& request, HttpResponse& resp) const;
183 
184   typedef boost::function<void(HttpRequest* req, HttpResponse* resp)> HandlerFunction;
185   void registerApiHandler(const string& url, const HandlerFunction& handler, bool allowPassword=false);
186   void registerWebHandler(const string& url, const HandlerFunction& handler);
187 
188   enum class LogLevel : uint8_t {
189     None = 0,                // No logs from requests at all
190     Normal = 10,             // A "common log format"-like line e.g. '127.0.0.1 "GET /apache_pb.gif HTTP/1.0" 200 2326'
191     Detailed = 20,           // The full request headers and body, and the full response headers and body
192   };
193 
setLogLevel(const string & level)194   void setLogLevel(const string& level) {
195     if (level == "none") {
196       d_loglevel = LogLevel::None;
197       return;
198     }
199 
200     if (level == "normal") {
201       d_loglevel = LogLevel::Normal;
202       return;
203     }
204 
205     if (level == "detailed") {
206       d_loglevel = LogLevel::Detailed;
207       return;
208     }
209 
210     throw PDNSException("Unknown webserver log level: " + level);
211   }
212 
setLogLevel(const LogLevel level)213   void setLogLevel(const LogLevel level) {
214     d_loglevel = level;
215   };
216 
getLogLevel()217   LogLevel getLogLevel() {
218     return d_loglevel;
219   };
220 
221 protected:
222   void registerBareHandler(const string& url, const HandlerFunction& handler);
223   void logRequest(const HttpRequest& req, const ComboAddress& remote) const;
224   void logResponse(const HttpResponse& resp, const ComboAddress& remote, const string& logprefix) const;
225 
createServer()226   virtual std::shared_ptr<Server> createServer() {
227     return std::make_shared<Server>(d_listenaddress, d_port);
228   }
229 
230   string d_listenaddress;
231   int d_port;
232   string d_password;
233   std::shared_ptr<Server> d_server;
234 
235   std::string d_apikey;
236   void apiWrapper(const WebServer::HandlerFunction& handler, HttpRequest* req, HttpResponse* resp, bool allowPassword);
237   std::string d_webserverPassword;
238   void webWrapper(const WebServer::HandlerFunction& handler, HttpRequest* req, HttpResponse* resp);
239 
240   ssize_t d_maxbodysize; // in bytes
241 
242   NetmaskGroup d_acl;
243 
244   const string d_logprefix = "[webserver] ";
245 
246   // Describes the amount of logging the webserver does
247   WebServer::LogLevel d_loglevel{WebServer::LogLevel::Detailed};
248 };
249