blob: 009ae6187465e23adb75cfde7df3fab794fe5068 [file] [log] [blame]
Ed Tanous911ac312017-08-15 09:37:42 -07001#pragma once
2
Ed Tanous911ac312017-08-15 09:37:42 -07003#include <dbus/connection.hpp>
4#include <dbus/endpoint.hpp>
5#include <dbus/filter.hpp>
6#include <dbus/match.hpp>
7#include <dbus/message.hpp>
Ed Tanousba9f9a62017-10-11 16:40:35 -07008#include <persistent_data_middleware.hpp>
9#include <token_authorization_middleware.hpp>
Ed Tanous911ac312017-08-15 09:37:42 -070010#include <fstream>
Ed Tanousba9f9a62017-10-11 16:40:35 -070011#include <streambuf>
12#include <string>
Ed Tanous2a866f82017-10-25 17:46:24 -070013#include <crow/app.h>
14#include <boost/algorithm/string.hpp>
Ed Tanous3dac7492017-08-02 13:46:20 -070015namespace crow {
16namespace redfish {
17
18template <typename... Middlewares>
Ed Tanousba9f9a62017-10-11 16:40:35 -070019void get_redfish_sub_routes(Crow<Middlewares...>& app, const std::string& url,
20 nlohmann::json& j) {
21 auto routes = app.get_routes(url);
22 for (auto& route : routes) {
23 auto redfish_sub_route =
24 route.substr(url.size(), route.size() - url.size() - 1);
25 // check if the route is at this level, and we didn't find and exact match
26 // also, filter out resources that start with $ to remove $metadata
27 if (!redfish_sub_route.empty() && redfish_sub_route[0] != '$' &&
28 redfish_sub_route.find('/') == std::string::npos) {
29 j[redfish_sub_route] = nlohmann::json{{"@odata.id", route}};
Ed Tanous911ac312017-08-15 09:37:42 -070030 }
Ed Tanousba9f9a62017-10-11 16:40:35 -070031 }
32}
Ed Tanous911ac312017-08-15 09:37:42 -070033
Ed Tanous2a866f82017-10-25 17:46:24 -070034std::string execute_process(const char* cmd) {
35 std::array<char, 128> buffer;
36 std::string result;
37 std::shared_ptr<FILE> pipe(popen(cmd, "r"), pclose);
38 if (!pipe) throw std::runtime_error("popen() failed!");
39 while (!feof(pipe.get())) {
40 if (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr)
41 result += buffer.data();
42 }
43 return result;
44}
45
Ed Tanousba9f9a62017-10-11 16:40:35 -070046template <typename... Middlewares>
47void request_routes(Crow<Middlewares...>& app) {
48 CROW_ROUTE(app, "/redfish/")
Ed Tanous911ac312017-08-15 09:37:42 -070049 .methods("GET"_method)([](const crow::request& req, crow::response& res) {
Ed Tanousba9f9a62017-10-11 16:40:35 -070050 res.json_value = {{"v1", "/redfish/v1/"}};
51 res.end();
52 });
53
54 CROW_ROUTE(app, "/redfish/v1/")
55 .methods(
56 "GET"_method)([&](const crow::request& req, crow::response& res) {
57 res.json_value = {
58 {"@odata.context", "/redfish/v1/$metadata#ServiceRoot.ServiceRoot"},
59 {"@odata.id", "/redfish/v1/"},
60 {"@odata.type", "#ServiceRoot.v1_1_1.ServiceRoot"},
61 {"Id", "RootService"},
62 {"Name", "Root Service"},
63 {"RedfishVersion", "1.1.0"},
64 {"Links",
65 {{"Sessions",
66 {{"@odata.id", "/redfish/v1/SessionService/Sessions/"}}}}}};
67
68 res.json_value["UUID"] =
69 app.template get_middleware<PersistentData::Middleware>()
70 .system_uuid;
71 get_redfish_sub_routes(app, "/redfish/v1/", res.json_value);
72 res.end();
73 });
74
Ed Tanousba9f9a62017-10-11 16:40:35 -070075 CROW_ROUTE(app, "/redfish/v1/Chassis/")
76 .methods("GET"_method)(
77 [&](const crow::request& req, crow::response& res) {
78 std::vector<std::string> entities;
79 /*std::ifstream f("~/system.json");
80
81 nlohmann::json input = nlohmann::json::parse(f);
82 for (auto it = input.begin(); it != input.end(); it++) {
83 auto value = it.value();
84 if (value["type"] == "Chassis") {
85 std::string str = value["name"];
86 entities.emplace_back(str);
87 }
88 }
89 */
90 res.json_value = {
91 {"@odata.context",
92 "/redfish/v1/$metadata#ChassisCollection.ChassisCollection"},
93 {"@odata.id", "/redfish/v1/Chassis"},
94 {"@odata.type", "#ChassisCollection.ChassisCollection"},
95 {"Name", "Chassis Collection"},
96 {"Members@odata.count", entities.size()}};
97
98 get_redfish_sub_routes(app, "/redfish/v1/Chassis", res.json_value);
99 res.end();
100 });
101
102 CROW_ROUTE(app, "/redfish/v1/AccountService/")
103 .methods(
104 "GET"_method)([&](const crow::request& req, crow::response& res) {
105 res.json_value = {
106 {"@odata.context",
107 "/redfish/v1/$metadata#AccountService.AccountService"},
108 {"@odata.id", "/redfish/v1/AccountService"},
109 {"@odata.type", "#AccountService.v1_1_0.AccountService"},
110 {"Id", "AccountService"},
111 {"Name", "Account Service"},
112 {"Description", "BMC User Accounts"},
113 {"Status",
114 // TODO(ed) health rollup
115 {{"State", "Enabled"}, {"Health", "OK"}, {"HealthRollup", "OK"}}},
116 {"ServiceEnabled", true},
117 {"MinPasswordLength", 1},
118 {"MaxPasswordLength", 20},
119 };
120 get_redfish_sub_routes(app, "/redfish/v1/AccountService",
121 res.json_value);
122 res.end();
123 });
124
125 CROW_ROUTE(app, "/redfish/v1/AccountService/Roles/")
126 .methods("GET"_method)(
127 [&](const crow::request& req, crow::response& res) {
128 res.json_value = {
129 {"@odata.context",
130 "/redfish/v1/$metadata#RoleCollection.RoleCollection"},
131 {"@odata.id", "/redfish/v1/AccountService/Roles"},
132 {"@odata.type", "#RoleCollection.RoleCollection"},
133 {"Name", "Account Service"},
134 {"Description", "BMC User Roles"},
135 {"Members@odata.count", 1},
136 {"Members",
137 {{"@odata.id",
138 "/redfish/v1/AccountService/Roles/Administrator"}}}};
139 get_redfish_sub_routes(app, "/redfish/v1/AccountService",
140 res.json_value);
141 res.end();
142 });
143
144 CROW_ROUTE(app, "/redfish/v1/AccountService/Roles/Administrator/")
145 .methods("GET"_method)(
146 [&](const crow::request& req, crow::response& res) {
147 res.json_value = {
148 {"@odata.context", "/redfish/v1/$metadata#Role.Role"},
149 {"@odata.id", "/redfish/v1/AccountService/Roles/Administrator"},
150 {"@odata.type", "#Role.v1_0_2.Role"},
151 {"Id", "Administrator"},
152 {"Name", "User Role"},
153 {"Description", "Administrator User Role"},
154 {"IsPredefined", true},
155 {"AssignedPrivileges",
156 {"Login", "ConfigureManager", "ConfigureUsers",
157 "ConfigureSelf", "ConfigureComponents"}}};
158 res.end();
159 });
160
161 CROW_ROUTE(app, "/redfish/v1/AccountService/Accounts/")
162 .methods(
163 "GET"_method)([&](const crow::request& req, crow::response& res) {
Ed Tanous911ac312017-08-15 09:37:42 -0700164 boost::asio::io_service io;
165 auto bus = std::make_shared<dbus::connection>(io, dbus::bus::session);
166 dbus::endpoint user_list("org.openbmc.UserManager",
167 "/org/openbmc/UserManager/Users",
168 "org.openbmc.Enrol", "UserList");
169 bus->async_method_call(
170 [&](const boost::system::error_code ec,
Ed Tanousba9f9a62017-10-11 16:40:35 -0700171 const std::vector<std::string>& users) {
Ed Tanous911ac312017-08-15 09:37:42 -0700172 if (ec) {
173 res.code = 500;
174 } else {
Ed Tanousba9f9a62017-10-11 16:40:35 -0700175 res.json_value = {
Ed Tanous911ac312017-08-15 09:37:42 -0700176 {"@odata.context",
177 "/redfish/v1/"
178 "$metadata#ManagerAccountCollection."
179 "ManagerAccountCollection"},
180 {"@odata.id", "/redfish/v1/AccountService/Accounts"},
181 {"@odata.type",
182 "#ManagerAccountCollection.ManagerAccountCollection"},
183 {"Name", "Accounts Collection"},
184 {"Description", "BMC User Accounts"},
185 {"Members@odata.count", users.size()}};
Ed Tanousba9f9a62017-10-11 16:40:35 -0700186 nlohmann::json member_array = nlohmann::json::array();
Ed Tanous911ac312017-08-15 09:37:42 -0700187 int user_index = 0;
188 for (auto& user : users) {
189 member_array.push_back(
Ed Tanousba9f9a62017-10-11 16:40:35 -0700190 {{"@odata.id",
191 "/redfish/v1/AccountService/Accounts/" +
192 std::to_string(++user_index)}});
Ed Tanous911ac312017-08-15 09:37:42 -0700193 }
Ed Tanousba9f9a62017-10-11 16:40:35 -0700194 res.json_value["Members"] = member_array;
Ed Tanous911ac312017-08-15 09:37:42 -0700195 }
196 res.end();
Ed Tanousba9f9a62017-10-11 16:40:35 -0700197 },
198 user_list);
Ed Tanous3dac7492017-08-02 13:46:20 -0700199 });
200
201 CROW_ROUTE(app, "/redfish/v1/AccountService/Accounts/<int>/")
Ed Tanousba9f9a62017-10-11 16:40:35 -0700202 .methods("GET"_method)([](const crow::request& req, crow::response& res,
203 int account_index) {
204 res.json_value = {
Ed Tanous3dac7492017-08-02 13:46:20 -0700205 {"@odata.context",
206 "/redfish/v1/$metadata#ManagerAccount.ManagerAccount"},
207 {"@odata.id", "/redfish/v1/AccountService/Accounts/1"},
208 {"@odata.type", "#ManagerAccount.v1_0_3.ManagerAccount"},
209 {"Id", "1"},
210 {"Name", "User Account"},
211 {"Description", "User Account"},
212 {"Enabled", false},
213 {"Password", nullptr},
214 {"UserName", "anonymous"},
215 {"RoleId", "NoAccess"},
216 {"Links",
217 {{"Role",
218 {{"@odata.id", "/redfish/v1/AccountService/Roles/NoAccess"}}}}}};
Ed Tanousba9f9a62017-10-11 16:40:35 -0700219 res.end();
220 });
221
222 CROW_ROUTE(app, "/redfish/v1/SessionService/")
223 .methods(
224 "GET"_method)([&](const crow::request& req, crow::response& res) {
225 res.json_value = {
226 {"@odata.context",
227 "/redfish/v1/$metadata#SessionService.SessionService"},
228 {"@odata.id", "/redfish/v1/SessionService"},
229 {"@odata.type", "#SessionService.v1_1_1.SessionService"},
230 {"Id", "SessionService"},
231 {"Name", "SessionService"},
232 {"Description", "SessionService"},
233 {"Status",
234 {{"State", "Enabled"}, {"Health", "OK"}, {"HealthRollup", "OK"}}},
235 {"ServiceEnabled", true},
236 // TODO(ed) converge with session timeouts once they exist
237 // Bogus number for now
238 {"SessionTimeout", 1800}};
239 get_redfish_sub_routes(app, "/redfish/v1/AccountService",
240 res.json_value);
241 res.end();
242 });
243
244 CROW_ROUTE(app, "/redfish/v1/SessionService/Sessions/")
245 .methods("POST"_method, "GET"_method)([&](const crow::request& req,
246 crow::response& res) {
247 auto& data_middleware =
248 app.template get_middleware<PersistentData::Middleware>();
249 if (req.method == "POST"_method) {
250 // call with exceptions disabled
251 auto login_credentials =
252 nlohmann::json::parse(req.body, nullptr, false);
253 if (login_credentials.is_discarded()) {
254 res.code = 400;
255 res.end();
256 return;
257 }
258 // check for username/password in the root object
259 auto user_it = login_credentials.find("UserName");
260 auto pass_it = login_credentials.find("Password");
261 if (user_it == login_credentials.end() ||
262 pass_it == login_credentials.end()) {
263 res.code = 400;
264 res.end();
265 return;
266 }
267
268 std::string username = user_it->get<const std::string>();
269 std::string password = pass_it->get<const std::string>();
270 if (username.empty() || password.empty()) {
271 res.code = 400;
272 res.end();
273 return;
274 }
275
276 if (!pam_authenticate_user(username, password)) {
277 res.code = 401;
278 res.end();
279 return;
280 }
281 auto session = data_middleware.generate_user_session(username);
282 res.code = 200;
283 res.add_header("X-Auth-Token", session.session_token);
284 res.json_value = {
285 {"@odata.context", "/redfish/v1/$metadata#Session"},
286 {"@odata.id",
287 "/redfish/v1/SessionService/Sessions/" + session.unique_id},
288 {"@odata.type", "#Session.v1_0_3.Session"},
289 {"Id", session.unique_id},
290 {"Name", "User Session"},
291 {"Description", "Manager User Session"},
292 {"UserName", username}};
293 } else { // assume get
294 res.json_value = {
295 {"@odata.context",
296 "/redfish/v1/$metadata#SessionCollection.SessionCollection"},
297 {"@odata.id", "/redfish/v1/SessionService/Sessions"},
298 {"@odata.type", "#SessionCollection.SessionCollection"},
299 {"Name", "Session Collection"},
300 {"Description", "Session Collection"},
301 {"Members@odata.count", data_middleware.auth_tokens.size()}
302
303 };
304 nlohmann::json member_array = nlohmann::json::array();
305 for (auto& session : data_middleware.auth_tokens) {
306 member_array.push_back({{"@odata.id",
307 "/redfish/v1/SessionService/Sessions/" +
308 session.second.unique_id}});
309 }
310 res.json_value["Members"] = member_array;
311 }
312 res.end();
313 });
314
315 CROW_ROUTE(app, "/redfish/v1/SessionService/Sessions/<str>/")
316 .methods("GET"_method, "DELETE"_method)([&](const crow::request& req,
317 crow::response& res,
318 const std::string& session) {
319 auto& data_middleware =
320 app.template get_middleware<PersistentData::Middleware>();
Ed Tanous710adfc2017-10-24 17:04:52 -0700321 // TODO(Ed) this is inefficient
322 auto session_it = data_middleware.auth_tokens.begin();
323 while (session_it != data_middleware.auth_tokens.end()) {
324 if (session_it->second.unique_id == session) {
325 break;
326 }
327 session_it++;
328 }
329
Ed Tanousba9f9a62017-10-11 16:40:35 -0700330 if (session_it == data_middleware.auth_tokens.end()) {
331 res.code = 404;
332 res.end();
333 return;
334 }
335 if (req.method == "DELETE"_method) {
336 data_middleware.auth_tokens.erase(session_it);
337 res.code = 200;
338 } else { // assume get
339 res.json_value = {
340 {"@odata.context", "/redfish/v1/$metadata#Session.Session"},
341 {"@odata.id", "/redfish/v1/SessionService/Sessions/" + session},
342 {"@odata.type", "#Session.v1_0_3.Session"},
343 {"Id", session_it->second.unique_id},
344 {"Name", "User Session"},
345 {"Description", "Manager User Session"},
346 {"UserName", session_it->second.username}};
347 }
348 res.end();
Ed Tanous3dac7492017-08-02 13:46:20 -0700349 });
Ed Tanous2a866f82017-10-25 17:46:24 -0700350
351 CROW_ROUTE(app, "/redfish/v1/Managers/")
352 .methods("GET"_method)(
353 [&](const crow::request& req, crow::response& res) {
354 res.json_value = {
355 {"@odata.context",
356 "/redfish/v1/$metadata#ManagerCollection.ManagerCollection"},
357 {"@odata.id", "/redfish/v1/Managers"},
358 {"@odata.type", "#ManagerCollection.ManagerCollection"},
359 {"Name", "Manager Collection"},
360 {"Members@odata.count", 1},
361 {"Members", {{{"@odata.id", "/redfish/v1/Managers/openbmc"}}}}};
362 res.end();
363 });
364
365 CROW_ROUTE(app, "/redfish/v1/Managers/openbmc/")
366 .methods(
367 "GET"_method)([&](const crow::request& req, crow::response& res) {
368 time_t t = time(NULL);
369 tm* mytime = std::localtime(&t);
370 if (mytime == nullptr) {
371 res.code = 500;
372 res.end();
373 return;
374 }
375 std::array<char, 100> time_buffer;
376 std::size_t len = std::strftime(time_buffer.data(), time_buffer.size(),
377 "%FT%TZ", mytime);
378 if (len == 0) {
379 res.code = 500;
380 res.end();
381 return;
382 }
383 res.json_value = {
384 {"@odata.context", "/redfish/v1/$metadata#Manager.Manager"},
385 {"@odata.id", "/redfish/v1/Managers/openbmc"},
386 {"@odata.type", "#Manager.v1_3_0.Manager"},
387 {"Id", "openbmc"},
388 {"Name", "OpenBmc Manager"},
389 {"Description", "Baseboard Management Controller"},
390 {"UUID",
391 app.template get_middleware<PersistentData::Middleware>()
392 .system_uuid},
393 {"Model", "OpenBmc"}, // TODO(ed), get model
394 {"DateTime", time_buffer.data()},
395 {"Status",
396 {{"State", "Enabled"}, {"Health", "OK"}, {"HealthRollup", "OK"}}},
397 {"FirmwareVersion", "1234456789"}, // TODO(ed) get fwversion
398 {"PowerState", "On"}};
399 get_redfish_sub_routes(app, "/redfish/v1/Managers/openbmc/",
400 res.json_value);
401 res.end();
402 });
403
404 CROW_ROUTE(app, "/redfish/v1/Managers/NetworkProtocol/")
405 .methods(
406 "GET"_method)([&](const crow::request& req, crow::response& res) {
407 std::array<char, HOST_NAME_MAX> hostname;
408 if (gethostname(hostname.data(), hostname.size()) != 0) {
409 res.code = 500;
410 res.end();
411 return;
412 }
413 res.json_value = {
414 {"@odata.context",
415 "/redfish/v1/"
416 "$metadata#ManagerNetworkProtocol.ManagerNetworkProtocol"},
417 {"@odata.id", "/redfish/v1/Managers/BMC/NetworkProtocol"},
418 {"@odata.type",
419 "#ManagerNetworkProtocol.v1_1_0.ManagerNetworkProtocol"},
420 {"Id", "NetworkProtocol"},
421 {"Name", "Manager Network Protocol"},
422 {"Description", "Manager Network Service"},
423 {"Status",
424 {{"State", "Enabled"}, {"Health", "OK"}, {"HealthRollup", "OK"}}},
425 {"HostName", hostname.data()}}; // TODO(ed) get hostname
426 std::string netstat_out = execute_process("netstat -tuln");
427
428 std::map<int, const char*> service_types{{22, "SSH"},
429 {443, "HTTPS"},
430 {1900, "SSDP"},
431 {623, "IPMI"},
432 {427, "SLP"}};
433
434 std::vector<std::string> lines;
435 boost::split(lines, netstat_out, boost::is_any_of("\n"));
436 auto lines_it = lines.begin();
437 lines_it++; // skip the netstat header
438 lines_it++;
439 while (lines_it != lines.end()) {
440 std::vector<std::string> columns;
441 boost::split(columns, *lines_it, boost::is_any_of("\t "),
442 boost::token_compress_on);
443 if (columns.size() >= 5) {
444 std::size_t found = columns[3].find_last_of(":");
445 if (found != std::string::npos) {
446 std::string port_str = columns[3].substr(found + 1);
447 int port = std::stoi(port_str.c_str());
448 auto type_it = service_types.find(port);
449 if (type_it != service_types.end()) {
450 type_it->second;
451 res.json_value[type_it->second] = {{"ProtocolEnabled", true},
452 {"Port", port}};
453 }
454 }
455 }
456 lines_it++;
457 }
458
459 get_redfish_sub_routes(app, "/redfish/v1/", res.json_value);
460 res.end();
461 });
Ed Tanous3dac7492017-08-02 13:46:20 -0700462}
Ed Tanous911ac312017-08-15 09:37:42 -0700463} // namespace redfish
464} // namespace crow