Fawkes API  Fawkes Development Version
webview_thread.cpp
1 
2 /***************************************************************************
3  * webview_thread.cpp - Thread that handles web interface requests
4  *
5  * Created: Mon Oct 13 17:51:31 2008 (I5 Developer's Day)
6  * Copyright 2006-2018 Tim Niemueller [www.niemueller.de]
7  ****************************************************************************/
8 
9 /* This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU Library General Public License for more details.
18  *
19  * Read the full text in the LICENSE.GPL file in the doc directory.
20  */
21 
22 #include "webview_thread.h"
23 
24 #include "rest_processor.h"
25 #include "service_browse_handler.h"
26 #include "static_processor.h"
27 #include "user_verifier.h"
28 
29 #include <core/exceptions/system.h>
30 #include <core/version.h>
31 #include <sys/wait.h>
32 #include <utils/misc/string_conversions.h>
33 #include <utils/system/file.h>
34 #include <utils/system/hostinfo.h>
35 #include <webview/page_reply.h>
36 #include <webview/request_dispatcher.h>
37 #include <webview/server.h>
38 #include <webview/url_manager.h>
39 
40 using namespace fawkes;
41 
42 /** @class WebviewThread "webview_thread.h"
43  * Webview Thread.
44  * This thread runs the HTTP server and handles requests via the
45  * WebRequestDispatcher.
46  * @author Tim Niemueller
47  */
48 
49 /** Constructor.
50  * @param enable_tp true to enable thread pool setting the thread to
51  * wait-for-wakeup mode, falso to run request processing in this
52  * thread.
53  */
55 : Thread("WebviewThread", enable_tp ? Thread::OPMODE_WAITFORWAKEUP : Thread::OPMODE_CONTINUOUS)
56 {
57  cfg_use_thread_pool_ = enable_tp;
58 
59  if (!enable_tp)
61 }
62 
63 WebviewThread::~WebviewThread()
64 {
65 }
66 
67 void
69 {
70  cfg_port_ = config->get_uint("/webview/port");
71 
72  WebReply::set_caching_default(config->get_bool("/webview/client_side_caching"));
73 
74  webview_service_ = NULL;
75  service_browse_handler_ = NULL;
76  dispatcher_ = NULL;
77 
78  cfg_use_tls_ = false;
79  try {
80  cfg_use_tls_ = config->get_bool("/webview/tls/enable");
81  } catch (Exception &e) {
82  }
83 
84  cfg_use_ipv4_ = config->get_bool("/network/ipv4/enable");
85  cfg_use_ipv6_ = config->get_bool("/network/ipv6/enable");
86 
87  if (cfg_use_tls_) {
88  cfg_tls_create_ = false;
89  try {
90  cfg_tls_create_ = config->get_bool("/webview/tls/create");
91  } catch (Exception &e) {
92  }
93 
94  cfg_tls_key_ = config->get_string("/webview/tls/key-file");
95  cfg_tls_cert_ = config->get_string("/webview/tls/cert-file");
96 
97  try {
98  cfg_tls_cipher_suite_ = config->get_string("/webview/tls/cipher-suite");
99  logger->log_debug(name(), "Using cipher suite %s", cfg_tls_cipher_suite_.c_str());
100  } catch (Exception &e) {
101  }
102 
103  if (cfg_tls_key_[0] != '/')
104  cfg_tls_key_ = std::string(CONFDIR "/") + cfg_tls_key_;
105 
106  if (cfg_tls_cert_[0] != '/')
107  cfg_tls_cert_ = std::string(CONFDIR "/") + cfg_tls_cert_;
108 
109  logger->log_debug(name(),
110  "Key file: %s Cert file: %s",
111  cfg_tls_key_.c_str(),
112  cfg_tls_cert_.c_str());
113 
114  if (!File::exists(cfg_tls_key_.c_str())) {
115  if (File::exists(cfg_tls_cert_.c_str())) {
116  throw Exception("Key file %s does not exist, but certificate file %s "
117  "does",
118  cfg_tls_key_.c_str(),
119  cfg_tls_cert_.c_str());
120  } else if (cfg_tls_create_) {
121  tls_create(cfg_tls_key_.c_str(), cfg_tls_cert_.c_str());
122  } else {
123  throw Exception("Key file %s does not exist", cfg_tls_key_.c_str());
124  }
125  } else if (!File::exists(cfg_tls_cert_.c_str())) {
126  throw Exception("Certificate file %s does not exist, but key file %s "
127  "does",
128  cfg_tls_key_.c_str(),
129  cfg_tls_cert_.c_str());
130  }
131  }
132 
133  if (cfg_use_thread_pool_) {
134  cfg_num_threads_ = config->get_uint("/webview/thread-pool/num-threads");
135  }
136 
137  cfg_use_basic_auth_ = false;
138  try {
139  cfg_use_basic_auth_ = config->get_bool("/webview/use_basic_auth");
140  } catch (Exception &e) {
141  }
142  cfg_basic_auth_realm_ = "Fawkes Webview";
143  try {
144  cfg_basic_auth_realm_ = config->get_bool("/webview/basic_auth_realm");
145  } catch (Exception &e) {
146  }
147 
148  cfg_access_log_ = "";
149  try {
150  cfg_access_log_ = config->get_string("/webview/access_log");
151  } catch (Exception &e) {
152  }
153 
154  bool cfg_cors_allow_all = false;
155  try {
156  cfg_cors_allow_all = config->get_bool("/webview/cors/allow/all");
157  } catch (Exception &e) {
158  }
159  std::vector<std::string> cfg_cors_origins;
160  try {
161  cfg_cors_origins = config->get_strings("/webview/cors/allow/origins");
162  } catch (Exception &e) {
163  }
164  unsigned int cfg_cors_max_age = 0;
165  try {
166  cfg_cors_max_age = config->get_uint("/webview/cors/max-age");
167  } catch (Exception &e) {
168  }
169 
170  webview_service_ =
171  new NetworkService(nnresolver, "Fawkes Webview on %h", "_http._tcp", cfg_port_);
172  webview_service_->add_txt("fawkesver=%u.%u.%u",
173  FAWKES_VERSION_MAJOR,
174  FAWKES_VERSION_MINOR,
175  FAWKES_VERSION_MICRO);
176  service_browse_handler_ = new WebviewServiceBrowseHandler(logger, webview_service_);
177 
178  dispatcher_ = new WebRequestDispatcher(webview_url_manager);
179 
180  try {
181  webserver_ = new WebServer(cfg_port_, dispatcher_, logger);
182 
183  (*webserver_)
184  .setup_ipv(cfg_use_ipv4_, cfg_use_ipv6_)
185  .setup_cors(cfg_cors_allow_all, std::move(cfg_cors_origins), cfg_cors_max_age);
186 
187  if (cfg_use_tls_) {
188  webserver_->setup_tls(cfg_tls_key_.c_str(),
189  cfg_tls_cert_.c_str(),
190  cfg_tls_cipher_suite_.empty() ? NULL : cfg_tls_cipher_suite_.c_str());
191  }
192 
193  if (cfg_use_thread_pool_) {
194  webserver_->setup_thread_pool(cfg_num_threads_);
195  }
196 
197  if (cfg_use_basic_auth_) {
198  user_verifier_ = new WebviewUserVerifier(config, logger);
199  webserver_->setup_basic_auth(cfg_basic_auth_realm_.c_str(), user_verifier_);
200  }
202 
203  if (cfg_access_log_ != "") {
204  logger->log_debug(name(), "Setting up access log %s", cfg_access_log_.c_str());
205  webserver_->setup_access_log(cfg_access_log_.c_str());
206  }
207  } catch (Exception &e) {
208  delete webview_service_;
209  delete service_browse_handler_;
210  delete dispatcher_;
211  throw;
212  }
213  // get all directories for the static processor
214  std::vector<std::string> static_dirs = config->get_strings("/webview/htdocs/dirs");
215  std::string catchall_file;
216  try {
217  catchall_file = config->get_string("/webview/htdocs/catchall-file");
218  } catch (Exception &e) {
219  };
220  std::string mime_file = config->get_string("/webview/htdocs/mime-file");
221  static_dirs = StringConversions::resolve_paths(static_dirs);
222  std::string static_base_url = catchall_file.empty() ? "/static/" : "/";
223  static_processor_ = new WebviewStaticRequestProcessor(
224  webview_url_manager, static_base_url, static_dirs, catchall_file, mime_file, logger);
225  rest_processor_ =
227 
228  try {
229  cfg_explicit_404_ = config->get_strings("/webview/explicit-404");
230  for (const auto &u : cfg_explicit_404_) {
231  webview_url_manager->add_handler(WebRequest::METHOD_GET,
232  u,
233  std::bind(&WebviewThread::produce_404, this),
234  10000);
235  }
236  } catch (Exception &e) {
237  } // ignored, no explicit 404
238 
239  std::string afs;
240  if (cfg_use_ipv4_ && cfg_use_ipv6_) {
241  afs = "IPv4,IPv6";
242  } else if (cfg_use_ipv4_) {
243  afs = "IPv4";
244  } else if (cfg_use_ipv6_) {
245  afs = "IPv6";
246  }
247  webserver_->start();
248  logger->log_info("WebviewThread",
249  "Listening for HTTP%s connections on port %u (%s)",
250  cfg_use_tls_ ? "S" : "",
251  cfg_port_,
252  afs.c_str());
253 
254  service_publisher->publish_service(webview_service_);
255  service_browser->watch_service("_http._tcp", service_browse_handler_);
256 }
257 
258 void
260 {
261  try {
262  service_publisher->unpublish_service(webview_service_);
263  } catch (Exception &e) {
264  } // ignored, can happen if avahi-daemon not running
265  try {
266  service_browser->unwatch_service("_http._tcp", service_browse_handler_);
267  } catch (Exception &e) {
268  } // ignored, can happen if avahi-daemon not running
269 
270  for (const auto &u : cfg_explicit_404_) {
271  webview_url_manager->remove_handler(WebRequest::METHOD_GET, u);
272  }
273 
274  delete webserver_;
275 
276  delete webview_service_;
277  delete service_browse_handler_;
278 
279  delete dispatcher_;
280  delete static_processor_;
281  delete rest_processor_;
282  dispatcher_ = NULL;
283 }
284 
285 void
287 {
288  if (!cfg_use_thread_pool_)
289  webserver_->process();
290 }
291 
292 void
293 WebviewThread::tls_create(const char *tls_key_file, const char *tls_cert_file)
294 {
295  logger->log_info(name(),
296  "Creating TLS key and certificate. "
297  "This may take a while...");
298  HostInfo h;
299 
300  char *cmd;
301  if (asprintf(&cmd,
302  "openssl req -new -x509 -batch -nodes -days 365 "
303  "-subj \"/C=XX/L=World/O=Fawkes/CN=%s.local\" "
304  "-out \"%s\" -keyout \"%s\" >/dev/null 2>&1",
305  h.short_name(),
306  tls_cert_file,
307  tls_key_file)
308  == -1) {
309  throw OutOfMemoryException("Webview/TLS: Could not generate OpenSSL string");
310  }
311 
312  int status = system(cmd);
313  free(cmd);
314 
315  if (WEXITSTATUS(status) != 0) {
316  throw Exception("Failed to auto-generate key/certificate pair");
317  }
318 }
319 
320 WebReply *
321 WebviewThread::produce_404()
322 {
323  return new StaticWebReply(WebReply::HTTP_NOT_FOUND, "Not found\n");
324 }
REST API web processor.
Browse handler to detect other Webview instances on the network.
Static file web processor.
virtual void finalize()
Finalize the thread.
virtual void init()
Initialize the thread.
WebviewThread(bool enable_tp)
Constructor.
virtual void loop()
Code to execute in the thread.
Webview user verification.
Definition: user_verifier.h:34
Configuration * config
This is the Configuration member used to access the configuration.
Definition: configurable.h:41
virtual unsigned int get_uint(const char *path)=0
Get value from configuration which is of type unsigned int.
virtual bool get_bool(const char *path)=0
Get value from configuration which is of type bool.
virtual std::vector< std::string > get_strings(const char *path)=0
Get list of values from configuration which is of type string.
virtual std::string get_string(const char *path)=0
Get value from configuration which is of type string.
Base class for exceptions in Fawkes.
Definition: exception.h:36
Host information.
Definition: hostinfo.h:32
const char * short_name()
Get short hostname (up to first dot).
Definition: hostinfo.cpp:109
virtual void log_debug(const char *component, const char *format,...)=0
Log debug message.
virtual void log_info(const char *component, const char *format,...)=0
Log informational message.
Logger * logger
This is the Logger member used to access the logger.
Definition: logging.h:41
NetworkNameResolver * nnresolver
Network name resolver to lookup IP addresses of hostnames and vice versa.
Definition: network.h:45
ServicePublisher * service_publisher
Service publisher to publish services on the network.
Definition: network.h:46
ServiceBrowser * service_browser
Service browser to browse services on the network.
Definition: network.h:47
Representation of a service announced or found via service discovery (i.e.
Definition: service.h:38
void add_txt(const char *format,...)
Add a TXT record.
Definition: service.cpp:313
System ran out of memory and desired operation could not be fulfilled.
Definition: system.h:32
virtual void watch_service(const char *service_type, ServiceBrowseHandler *h)=0
Add browse handler for specific service.
virtual void unwatch_service(const char *service_type, ServiceBrowseHandler *h)=0
Remove browse handler for specific service.
virtual void unpublish_service(NetworkService *service)=0
Revoke service publication.
virtual void publish_service(NetworkService *service)=0
Publish service.
Static web reply.
Definition: reply.h:136
Thread class encapsulation of pthreads.
Definition: thread.h:46
void set_prepfin_conc_loop(bool concurrent=true)
Set concurrent execution of prepare_finalize() and loop().
Definition: thread.cpp:716
const char * name() const
Get name of thread.
Definition: thread.h:100
Basic web reply.
Definition: reply.h:34
Web request dispatcher.
Encapsulation of the libmicrohttpd webserver.
Definition: server.h:44
WebServer & setup_access_log(const char *filename)
Setup access log.
Definition: server.cpp:276
void process()
Process requests.
Definition: server.cpp:322
WebServer & setup_tls(const char *key_pem_filepath, const char *cert_pem_filepath, const char *cipher_suite=WEBVIEW_DEFAULT_CIPHERS)
Setup Transport Layer Security (encryption),.
Definition: server.cpp:76
WebServer & setup_thread_pool(unsigned int num_threads)
Setup thread pool.
Definition: server.cpp:130
WebServer & setup_basic_auth(const char *realm, WebUserVerifier *verifier)
Setup basic authentication.
Definition: server.cpp:265
void start()
Start daemon and enable processing requests.
Definition: server.cpp:140
WebServer & setup_request_manager(WebRequestManager *request_manager)
Setup this server as request manager.
Definition: server.cpp:288
void remove_handler(WebRequest::Method method, const std::string &path)
Remove a request processor.
Definition: url_manager.cpp:84
void add_handler(WebRequest::Method method, const std::string &path, Handler handler)
Add a request processor.
Definition: url_manager.cpp:54
WebRequestManager * webview_request_manager
Webview request manager.
Definition: webview.h:53
WebviewRestApiManager * webview_rest_api_manager
Webview REST API manager.
Definition: webview.h:55
WebUrlManager * webview_url_manager
Webview request processor manager.
Definition: webview.h:49
Fawkes library namespace.