HttpsServer.cpp 11.6 KB
Newer Older
1
2
3
4
5
6
7
8
/*
 * HttpsServer.cpp
 *
 *  Created on: 25.05.2018
 *      Author: Micha Mueller
 */

#include "HttpsServer.h"
9
#include "timestamp.h"
10

11
#include <iostream>
12
13
#include <memory>
#include <functional>
14
#include <string>
15

16
17
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/info_parser.hpp>
Micha Mueller's avatar
Micha Mueller committed
18
19
#include <boost/algorithm/string/split.hpp>

20
21
#define LOGH(sev) LOG(sev) << "HttpsServer: "

22
23
HttpsServer::requestHandler::requestHandler(HttpsServer& httpsServer) :	_httpsServer(httpsServer) {}

24
void HttpsServer::requestHandler::operator()(server::request const &request, server::connection_ptr connection) {
25
	//first log some info about client
26
27
28
	server::string_type ip = source(request);
	unsigned int port = request.source_port;

29
	LOGH(info) << ip << ":" << port << " connected";
30

31
	//set appropriate default value to connection status
Micha Mueller's avatar
Micha Mueller committed
32
33
34
	connection->set_status(server::connection::internal_server_error);

	std::ostringstream data;
35
36

	boost::network::uri::uri uri("https://" + request.destination);
Micha Mueller's avatar
Micha Mueller committed
37
	server::string_type method = request.method;
38
39
40
41
42
	server::string_type path = uri.path();
	server::string_type query = uri.query();

	std::string response = "";
	std::string auth_value = "";
43
	bool json = false;
44
45
	std::vector<std::string> pathStrs;
	std::vector<std::pair<std::string, std::string>> queries;
46

Micha Mueller's avatar
Micha Mueller committed
47
48
	//first check if request is supported at all
	if (method != "GET" && method != "PUT") {
49
		LOGH(warning) << "Unsupported " << method << " request was made";
Micha Mueller's avatar
Micha Mueller committed
50
		connection->set_status(server::connection::not_supported);
51
52
		goto error;
	}
53

54
	LOGH(info) << method << " request of " << request.destination << " was made";
Micha Mueller's avatar
Micha Mueller committed
55

56
57
58
59
60
61
62
63
	//do some string processing
	//split path into its hierarchical parts
	if (path.size() >= 2) {
		if (path[0] == '/') {
			path.erase(0,1);
		}
		if (path[path.size() -1] == '/') {
			path.erase(path.size() -1);
Micha Mueller's avatar
Micha Mueller committed
64
		}
65

66
67
68
69
		boost::split(pathStrs, path, boost::is_any_of("/"), boost::token_compress_off);
	}

	//split query part into the individual queries (key-value pairs)
Micha Mueller's avatar
Micha Mueller committed
70
71
	{	//do not remove the enclosing brackets
		//need to encapsulate this code block to keep queryStrs local
Micha Mueller's avatar
Micha Mueller committed
72
73
74
75
		std::vector<std::string> queryStrs;
		boost::split(queryStrs, query, boost::is_any_of(";"), boost::token_compress_on);
		for(auto& key : queryStrs) {
			size_t pos = key.find("=");
76
			if (pos != std::string::npos) {
Micha Mueller's avatar
Micha Mueller committed
77
78
79
80
				std::string value;
				value = key.substr(pos+1);
				key.erase(pos);
				queries.push_back(std::make_pair(key, value));
81
82
			}
		}
83
84
	}
	//finished string processing
85

86
	for (auto& p : queries) {
87
		//authkey is required in every case
88
89
		if (p.first == "authkey") {
			auth_value = p.second;
90
91
92
93
		} else if (p.first == "json") {
			if (stoi(p.second) > 0) {
				json = true;
			}
94
		}
95
	}
96

97
	if (pathStrs.size() < 1) {
98
		LOGH(warning) << "Received malformed request: No first path part";
99
100
101
102
103
104
105
106
		connection->set_status(server::connection::bad_request);
		goto error;
	}

	//select code depending on request
	if (method == "GET") {
		if (pathStrs[0] == "help") {
			response = "dcdbpusher RESTful API cheatsheet:\n"
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
			" -GET:  /help     This help message\n"
			"        /plugins  List of currently loaded plugins (Discovery)\n"
			"        /[plugin]/sensors\n"
			"                  List of currently running sensors which belong\n"
			"                  to the specified plugin (Discovery)\n"
			"        /[plugin]/[sensor]/avg?interval=[timeInSec]\n"
			"                  Average of last sensor readings from the last\n"
			"                  [interval] seconds or of all cached readings\n"
			"                  if no interval is given\n"
			" -PUT:  /[plugin]/[start|stop|reload]\n"
			"                  Start/stop the sensors of the plugin or\n"
			"                  reload the plugin configuration\n"
			"\n"
			"All resources have to be prepended by host:port and need at\n"
			"least the query ?authkey=[token] at the end. Multiple queries\n"
			"need to be separated by semicolons(';')\n";
Micha Mueller's avatar
Micha Mueller committed
123
		} else {
124
125
126
127
			//first check permission
			if (!_httpsServer.check_authkey(auth_value, permission::GETReq)) {
				LOGH(warning) << "Provided authentication token has insufficient permissions";
				connection->set_status(server::connection::unauthorized);
128
129
				goto error;
			}
130
131
132
133
134
135
136
137
138
139
140
141
			
			if (pathStrs[0] == "plugins") {
				if (json) {
					boost::property_tree::ptree root, plugins;
					for(auto& p : _httpsServer._plugins) {
						plugins.put(p.id, "");
					}
					root.add_child("plugins", plugins);
					boost::property_tree::write_info(data, root);
				} else {
					for(auto& p : _httpsServer._plugins) {
						data << p.id << "\n";
142
143
					}
				}
144
			} else {
145
146
147
				//do some prior checks
				if (pathStrs.size() < 2) {
					LOGH(warning) << "Received malformed request: No second path part";
Micha Mueller's avatar
Micha Mueller committed
148
					connection->set_status(server::connection::bad_request);
149
150
					goto error;
				}
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
				
				if (pathStrs[1] == "sensors") {
					response = "Plugin not found!";
					connection->set_status(server::connection::not_found);
					
					for(auto& p : _httpsServer._plugins) {
						if (p.id == pathStrs[0]) {
							if (json) {
								boost::property_tree::ptree root, sensors;
								
								for(auto g : p.configurator->getSensorGroups()) {
									boost::property_tree::ptree group;
									for(auto s : g->getSensors()) {
										group.put(s->getName(), s->getMqtt());
									}
									sensors.add_child(g->getGroupName(), group);
								}
								root.add_child(p.id, sensors);
								boost::property_tree::write_info(data, root);
							} else {
								for(auto g : p.configurator->getSensorGroups()) {
									for(auto s : g->getSensors()) {
										data << g->getGroupName() << "." << s->getName() << " " << s->getMqtt() << "\n";
									}
								}
							}
							response = "";
							connection->set_status(server::connection::ok);
							break;
						}
Micha Mueller's avatar
Micha Mueller committed
181
					}
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
				} else {
					
					if (pathStrs.size() < 3) {
						LOGH(warning) << "Received malformed request: No third path part";
						connection->set_status(server::connection::bad_request);
						goto error;
					}
					
					if (pathStrs[2] != "avg") {
						LOGH(warning) << "Unknown action " << pathStrs[2] << " requested";
						connection->set_status(server::connection::not_supported);
						goto error;
					}
					
					std::string sensor = pathStrs[1];
					std::string action = pathStrs[2];
					uint64_t time = 0;
					
					for (auto& p : queries) {
						if (p.first == "interval") {
							time = getTimestamp();
							time -= S_TO_NS(std::stoul(p.second));
						}
					}
206

207
208
209
210
211
212
213
					//process actual request
					response = "Plugin not found!";
					connection->set_status(server::connection::not_found);
					
					for(auto& p : _httpsServer._plugins) {
						if (p.id == pathStrs[0]) {
							response = "Sensor not found!";
Michael Ott's avatar
Michael Ott committed
214
215
							for(const auto& g : p.configurator->getSensorGroups()) {
								for(const auto& s : g->getSensors()) {
216
									if (s->getName() == sensor) {
Michael Ott's avatar
Michael Ott committed
217
										response = pathStrs[0] + "::" + sensor + _httpsServer.calcAvg(*s, time);
218
219
220
										connection->set_status(server::connection::ok);
										break;
									}
221
222
223
								}
							}
						}
224
					}
225
226
227
228
				}
			}
		}
	} else if (method == "PUT") {
229
		
230
231
		//first check permission
		if (!_httpsServer.check_authkey(auth_value, permission::PUTReq)) {
232
			LOGH(warning) << "Provided authentication token has insufficient permissions";
233
234
235
			connection->set_status(server::connection::unauthorized);
			goto error;
		}
236
		
237
		if (pathStrs.size() < 2) {
238
			LOGH(warning) << "Received malformed request: No second path part";
239
240
241
			connection->set_status(server::connection::bad_request);
			goto error;
		}
242
		
243
		std::string action = pathStrs[1];
244
		
245
246
247
		//process actual request
		response = "Plugin not found!";
		connection->set_status(server::connection::not_found);
248
		
249
250
251
252
		//switch code depending on selected action
		if (action == "start") {
			for(auto& p : _httpsServer._plugins) {
				if (p.id == pathStrs[0]) {
253
					for(const auto& g : p.configurator->getSensorGroups()) {
254
						g->start();
255
					}
256
257
					response = "Plugin " + pathStrs[0] + ": Sensors started";
					connection->set_status(server::connection::ok);
258
259
260
261
262
263
					break;
				}
			}
		} else if (action == "stop") {
			for(auto& p : _httpsServer._plugins) {
				if (p.id == pathStrs[0]) {
264
					for(const auto& g : p.configurator->getSensorGroups()) {
265
						g->stop();
266
					}
267
268
					response = "Plugin " + pathStrs[0] + ": Sensors stopped";
					connection->set_status(server::connection::ok);
269
					break;
270
271
				}
			}
272
273
274
275
276
277
278
279
280
281
		} else if (action == "reload") {
			for(auto& p : _httpsServer._plugins) {
				if (p.id == pathStrs[0]) {
					//before modifying the plugin we need to ensure that we have exclusive access
					//therefore pause the only other concurrent user (MQTTPusher)
					_httpsServer._mqttPusher->halt();
					//wait until MQTTPusher is paused
					while (!_httpsServer._mqttPusher->isHalted()) {
						sleep(1);
					}
282
					
283
284
285
286
287
288
289
290
					if (p.configurator->reReadConfig()) {
						response = "Plugin " + pathStrs[0] + ": Configuration reloaded";
						connection->set_status(server::connection::ok);
					} else {
						response = "Plugin " + pathStrs[0] + ": Could not reload configuration";
						connection->set_status(server::connection::internal_server_error);
					}

291
					for(const auto& g : p.configurator->getSensorGroups()) {
292
						g->init(_httpsServer._io);
293
						g->start();
294
					}
295
					
296
297
298
299
300
301
302
303
304
					//continue MQTTPusher
					_httpsServer._mqttPusher->cont();
					break;
				}
			}
		} else {
			LOGH(warning) << "Unknown action " << pathStrs[1] << " requested";
			connection->set_status(server::connection::not_supported);
			goto error;
305
306
307
		}
	}

308
	LOGH(info) << "Responding: " << response;
309
310
311
312
313
	data << response << std::endl;

	//jump right here if an error was encountered.
	//An empty response will be send while the connections status should be set to an appropriate error state
	error:
314
	server::response_header headers[] = { {"Connection", "close"}, {"Content-Type", "text/plain"} };
315
316
317
318
319
	connection->set_headers(boost::make_iterator_range(headers, headers + 2));
	connection->write(data.str());
}

void HttpsServer::requestHandler::log(const server::string_type& message) {
320
	LOGH(error) << message;
321
}
322

323
324
HttpsServer::HttpsServer(restAPISettings_t restAPISettings, pluginVector_t& plugins, MQTTPusher* mqttPusher, boost::asio::io_service& io) :
		_plugins(plugins), _mqttPusher(mqttPusher), _io(io), _handler(*this) {
325

326
327
328
	std::shared_ptr<asio::ssl::context> ctx = std::make_shared<asio::ssl::context>(asio::ssl::context::sslv23);
	ctx->set_options(asio::ssl::context::default_workarounds | asio::ssl::context::no_sslv3 | asio::ssl::context::single_dh_use);

329
	// Set certificate, private key and DH parameters
330
	//ctx->set_password_callback(HttpsServer::password_callback);
331
332
333
	ctx->use_certificate_chain_file(restAPISettings.certificate);
	ctx->use_private_key_file(restAPISettings.privateKey, asio::ssl::context::pem);
	ctx->use_tmp_dh_file(restAPISettings.dhFile);
334

335
	server::options options(_handler);
336
	_server = new server(options.address(restAPISettings.restHost).port(restAPISettings.restPort).context(ctx));
337
338
339
340
341
342
}

HttpsServer::~HttpsServer() {
	delete _server;
}

Micha Mueller's avatar
Micha Mueller committed
343
std::string HttpsServer::calcAvg(SensorBase& s, uint64_t time) {
344
	int64_t avg = 0;
345
	const reading_t * const cache = s.getCache();
346
347

	unsigned count = 0;
Micha Mueller's avatar
Micha Mueller committed
348
	for(unsigned i = 0; i < s.getCacheSize(); i++) {
349
350
351
352
353
354
355
356
357
358
359
		if (cache[i].timestamp > time) {
			avg += cache[i].value;
			count++;
		}
	}
	if (count > 0)
		avg /= count;

	return " Average of last " + std::to_string(count) + " values is " + std::to_string(avg);
}

360
361
362
363
364
365
366
367
bool HttpsServer::check_authkey(const std::string& authkey, permission requiredPerm) {
	authkeyMap_t::iterator it = _authkeys.find(authkey);
	if (it != _authkeys.end()) {
		return it->second[requiredPerm];
	}
	return false;
}

368
369
370
371
372
/*
std::string HttpsServer::password_callback(std::size_t max_length, asio::ssl::context_base::password_purpose purpose) {
    return std::string("pwd");
}
*/