blob: 1a5ee74eeee4849b952106bb1b2786e5dbaf9a8f [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) {
Ed Tanous1c74de82017-10-26 13:58:28 -070021 std::vector<const std::string*> routes = app.get_routes(url);
22 for (auto route : routes) {
Ed Tanousba9f9a62017-10-11 16:40:35 -070023 auto redfish_sub_route =
Ed Tanous1c74de82017-10-26 13:58:28 -070024 route->substr(url.size(), route->size() - url.size() - 1);
Ed Tanousba9f9a62017-10-11 16:40:35 -070025 // 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) {
Ed Tanous1c74de82017-10-26 13:58:28 -070029 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
Ed Tanousba9f9a62017-10-11 16:40:35 -070054 CROW_ROUTE(app, "/redfish/v1/Chassis/")
55 .methods("GET"_method)(
56 [&](const crow::request& req, crow::response& res) {
57 std::vector<std::string> entities;
58 /*std::ifstream f("~/system.json");
59
60 nlohmann::json input = nlohmann::json::parse(f);
61 for (auto it = input.begin(); it != input.end(); it++) {
62 auto value = it.value();
63 if (value["type"] == "Chassis") {
64 std::string str = value["name"];
65 entities.emplace_back(str);
66 }
67 }
68 */
Ed Tanous1c74de82017-10-26 13:58:28 -070069
Ed Tanousba9f9a62017-10-11 16:40:35 -070070 res.json_value = {
71 {"@odata.context",
72 "/redfish/v1/$metadata#ChassisCollection.ChassisCollection"},
73 {"@odata.id", "/redfish/v1/Chassis"},
74 {"@odata.type", "#ChassisCollection.ChassisCollection"},
75 {"Name", "Chassis Collection"},
76 {"Members@odata.count", entities.size()}};
77
78 get_redfish_sub_routes(app, "/redfish/v1/Chassis", res.json_value);
79 res.end();
80 });
81
82 CROW_ROUTE(app, "/redfish/v1/AccountService/")
83 .methods(
84 "GET"_method)([&](const crow::request& req, crow::response& res) {
85 res.json_value = {
86 {"@odata.context",
87 "/redfish/v1/$metadata#AccountService.AccountService"},
88 {"@odata.id", "/redfish/v1/AccountService"},
89 {"@odata.type", "#AccountService.v1_1_0.AccountService"},
90 {"Id", "AccountService"},
91 {"Name", "Account Service"},
92 {"Description", "BMC User Accounts"},
93 {"Status",
94 // TODO(ed) health rollup
95 {{"State", "Enabled"}, {"Health", "OK"}, {"HealthRollup", "OK"}}},
96 {"ServiceEnabled", true},
97 {"MinPasswordLength", 1},
98 {"MaxPasswordLength", 20},
99 };
100 get_redfish_sub_routes(app, "/redfish/v1/AccountService",
101 res.json_value);
102 res.end();
103 });
104
105 CROW_ROUTE(app, "/redfish/v1/AccountService/Roles/")
106 .methods("GET"_method)(
107 [&](const crow::request& req, crow::response& res) {
108 res.json_value = {
109 {"@odata.context",
110 "/redfish/v1/$metadata#RoleCollection.RoleCollection"},
111 {"@odata.id", "/redfish/v1/AccountService/Roles"},
112 {"@odata.type", "#RoleCollection.RoleCollection"},
113 {"Name", "Account Service"},
114 {"Description", "BMC User Roles"},
115 {"Members@odata.count", 1},
116 {"Members",
117 {{"@odata.id",
118 "/redfish/v1/AccountService/Roles/Administrator"}}}};
119 get_redfish_sub_routes(app, "/redfish/v1/AccountService",
120 res.json_value);
121 res.end();
122 });
123
124 CROW_ROUTE(app, "/redfish/v1/AccountService/Roles/Administrator/")
125 .methods("GET"_method)(
126 [&](const crow::request& req, crow::response& res) {
127 res.json_value = {
128 {"@odata.context", "/redfish/v1/$metadata#Role.Role"},
129 {"@odata.id", "/redfish/v1/AccountService/Roles/Administrator"},
130 {"@odata.type", "#Role.v1_0_2.Role"},
131 {"Id", "Administrator"},
132 {"Name", "User Role"},
133 {"Description", "Administrator User Role"},
134 {"IsPredefined", true},
135 {"AssignedPrivileges",
136 {"Login", "ConfigureManager", "ConfigureUsers",
137 "ConfigureSelf", "ConfigureComponents"}}};
138 res.end();
139 });
140
141 CROW_ROUTE(app, "/redfish/v1/AccountService/Accounts/")
142 .methods(
143 "GET"_method)([&](const crow::request& req, crow::response& res) {
Ed Tanous911ac312017-08-15 09:37:42 -0700144 boost::asio::io_service io;
145 auto bus = std::make_shared<dbus::connection>(io, dbus::bus::session);
146 dbus::endpoint user_list("org.openbmc.UserManager",
147 "/org/openbmc/UserManager/Users",
148 "org.openbmc.Enrol", "UserList");
149 bus->async_method_call(
150 [&](const boost::system::error_code ec,
Ed Tanousba9f9a62017-10-11 16:40:35 -0700151 const std::vector<std::string>& users) {
Ed Tanous911ac312017-08-15 09:37:42 -0700152 if (ec) {
153 res.code = 500;
154 } else {
Ed Tanousba9f9a62017-10-11 16:40:35 -0700155 res.json_value = {
Ed Tanous911ac312017-08-15 09:37:42 -0700156 {"@odata.context",
157 "/redfish/v1/"
158 "$metadata#ManagerAccountCollection."
159 "ManagerAccountCollection"},
160 {"@odata.id", "/redfish/v1/AccountService/Accounts"},
161 {"@odata.type",
162 "#ManagerAccountCollection.ManagerAccountCollection"},
163 {"Name", "Accounts Collection"},
164 {"Description", "BMC User Accounts"},
165 {"Members@odata.count", users.size()}};
Ed Tanousba9f9a62017-10-11 16:40:35 -0700166 nlohmann::json member_array = nlohmann::json::array();
Ed Tanous911ac312017-08-15 09:37:42 -0700167 int user_index = 0;
Ed Tanousc963aa42017-10-27 16:00:19 -0700168 for (int user_index = 0; user_index < users.size();
169 user_index++) {
Ed Tanous911ac312017-08-15 09:37:42 -0700170 member_array.push_back(
Ed Tanousba9f9a62017-10-11 16:40:35 -0700171 {{"@odata.id",
172 "/redfish/v1/AccountService/Accounts/" +
Ed Tanousc963aa42017-10-27 16:00:19 -0700173 std::to_string(user_index)}});
Ed Tanous911ac312017-08-15 09:37:42 -0700174 }
Ed Tanousba9f9a62017-10-11 16:40:35 -0700175 res.json_value["Members"] = member_array;
Ed Tanous911ac312017-08-15 09:37:42 -0700176 }
177 res.end();
Ed Tanousba9f9a62017-10-11 16:40:35 -0700178 },
179 user_list);
Ed Tanous3dac7492017-08-02 13:46:20 -0700180 });
181
182 CROW_ROUTE(app, "/redfish/v1/AccountService/Accounts/<int>/")
Ed Tanousba9f9a62017-10-11 16:40:35 -0700183 .methods("GET"_method)([](const crow::request& req, crow::response& res,
184 int account_index) {
185 res.json_value = {
Ed Tanous3dac7492017-08-02 13:46:20 -0700186 {"@odata.context",
187 "/redfish/v1/$metadata#ManagerAccount.ManagerAccount"},
188 {"@odata.id", "/redfish/v1/AccountService/Accounts/1"},
189 {"@odata.type", "#ManagerAccount.v1_0_3.ManagerAccount"},
190 {"Id", "1"},
191 {"Name", "User Account"},
192 {"Description", "User Account"},
193 {"Enabled", false},
194 {"Password", nullptr},
195 {"UserName", "anonymous"},
196 {"RoleId", "NoAccess"},
197 {"Links",
198 {{"Role",
199 {{"@odata.id", "/redfish/v1/AccountService/Roles/NoAccess"}}}}}};
Ed Tanousba9f9a62017-10-11 16:40:35 -0700200 res.end();
201 });
202
203 CROW_ROUTE(app, "/redfish/v1/SessionService/")
204 .methods(
205 "GET"_method)([&](const crow::request& req, crow::response& res) {
206 res.json_value = {
207 {"@odata.context",
208 "/redfish/v1/$metadata#SessionService.SessionService"},
209 {"@odata.id", "/redfish/v1/SessionService"},
210 {"@odata.type", "#SessionService.v1_1_1.SessionService"},
211 {"Id", "SessionService"},
212 {"Name", "SessionService"},
213 {"Description", "SessionService"},
214 {"Status",
215 {{"State", "Enabled"}, {"Health", "OK"}, {"HealthRollup", "OK"}}},
216 {"ServiceEnabled", true},
217 // TODO(ed) converge with session timeouts once they exist
218 // Bogus number for now
219 {"SessionTimeout", 1800}};
220 get_redfish_sub_routes(app, "/redfish/v1/AccountService",
221 res.json_value);
222 res.end();
223 });
224
225 CROW_ROUTE(app, "/redfish/v1/SessionService/Sessions/")
226 .methods("POST"_method, "GET"_method)([&](const crow::request& req,
227 crow::response& res) {
Ed Tanousc963aa42017-10-27 16:00:19 -0700228 auto& session_store =
229 app.template get_middleware<PersistentData::Middleware>().sessions;
Ed Tanousba9f9a62017-10-11 16:40:35 -0700230 if (req.method == "POST"_method) {
231 // call with exceptions disabled
232 auto login_credentials =
233 nlohmann::json::parse(req.body, nullptr, false);
234 if (login_credentials.is_discarded()) {
235 res.code = 400;
236 res.end();
237 return;
238 }
239 // check for username/password in the root object
240 auto user_it = login_credentials.find("UserName");
241 auto pass_it = login_credentials.find("Password");
242 if (user_it == login_credentials.end() ||
243 pass_it == login_credentials.end()) {
244 res.code = 400;
245 res.end();
246 return;
247 }
248
249 std::string username = user_it->get<const std::string>();
250 std::string password = pass_it->get<const std::string>();
251 if (username.empty() || password.empty()) {
252 res.code = 400;
253 res.end();
254 return;
255 }
256
257 if (!pam_authenticate_user(username, password)) {
258 res.code = 401;
259 res.end();
260 return;
261 }
Ed Tanousc963aa42017-10-27 16:00:19 -0700262 auto session = session_store.generate_user_session(username);
Ed Tanousba9f9a62017-10-11 16:40:35 -0700263 res.code = 200;
264 res.add_header("X-Auth-Token", session.session_token);
265 res.json_value = {
266 {"@odata.context", "/redfish/v1/$metadata#Session"},
267 {"@odata.id",
268 "/redfish/v1/SessionService/Sessions/" + session.unique_id},
269 {"@odata.type", "#Session.v1_0_3.Session"},
270 {"Id", session.unique_id},
271 {"Name", "User Session"},
272 {"Description", "Manager User Session"},
273 {"UserName", username}};
274 } else { // assume get
Ed Tanousc963aa42017-10-27 16:00:19 -0700275 std::vector<const std::string*> session_ids =
276 session_store.get_unique_ids();
Ed Tanousba9f9a62017-10-11 16:40:35 -0700277 res.json_value = {
278 {"@odata.context",
279 "/redfish/v1/$metadata#SessionCollection.SessionCollection"},
280 {"@odata.id", "/redfish/v1/SessionService/Sessions"},
281 {"@odata.type", "#SessionCollection.SessionCollection"},
282 {"Name", "Session Collection"},
283 {"Description", "Session Collection"},
Ed Tanousc963aa42017-10-27 16:00:19 -0700284 {"Members@odata.count", session_ids.size()}
Ed Tanousba9f9a62017-10-11 16:40:35 -0700285
286 };
287 nlohmann::json member_array = nlohmann::json::array();
Ed Tanousc963aa42017-10-27 16:00:19 -0700288 for (auto session_uid : session_ids) {
289 member_array.push_back(
290 {{"@odata.id",
291 "/redfish/v1/SessionService/Sessions/" + *session_uid}});
Ed Tanousba9f9a62017-10-11 16:40:35 -0700292 }
293 res.json_value["Members"] = member_array;
294 }
295 res.end();
296 });
297
298 CROW_ROUTE(app, "/redfish/v1/SessionService/Sessions/<str>/")
Ed Tanousc963aa42017-10-27 16:00:19 -0700299 .methods("GET"_method, "DELETE"_method)([&](
300 const crow::request& req, crow::response& res,
301 const std::string& session_id) {
302 auto& session_store =
303 app.template get_middleware<PersistentData::Middleware>().sessions;
Ed Tanous710adfc2017-10-24 17:04:52 -0700304 // TODO(Ed) this is inefficient
Ed Tanousc963aa42017-10-27 16:00:19 -0700305 auto session = session_store.get_session_by_uid(session_id);
Ed Tanous710adfc2017-10-24 17:04:52 -0700306
Ed Tanousc963aa42017-10-27 16:00:19 -0700307 if (session == nullptr) {
Ed Tanousba9f9a62017-10-11 16:40:35 -0700308 res.code = 404;
309 res.end();
310 return;
311 }
312 if (req.method == "DELETE"_method) {
Ed Tanousc963aa42017-10-27 16:00:19 -0700313 session_store.remove_session(session);
Ed Tanousba9f9a62017-10-11 16:40:35 -0700314 res.code = 200;
315 } else { // assume get
316 res.json_value = {
317 {"@odata.context", "/redfish/v1/$metadata#Session.Session"},
Ed Tanousc963aa42017-10-27 16:00:19 -0700318 {"@odata.id",
319 "/redfish/v1/SessionService/Sessions/" + session->unique_id},
Ed Tanousba9f9a62017-10-11 16:40:35 -0700320 {"@odata.type", "#Session.v1_0_3.Session"},
Ed Tanousc963aa42017-10-27 16:00:19 -0700321 {"Id", session->unique_id},
Ed Tanousba9f9a62017-10-11 16:40:35 -0700322 {"Name", "User Session"},
323 {"Description", "Manager User Session"},
Ed Tanousc963aa42017-10-27 16:00:19 -0700324 {"UserName", session->username}};
Ed Tanousba9f9a62017-10-11 16:40:35 -0700325 }
326 res.end();
Ed Tanous3dac7492017-08-02 13:46:20 -0700327 });
Ed Tanous2a866f82017-10-25 17:46:24 -0700328
329 CROW_ROUTE(app, "/redfish/v1/Managers/")
330 .methods("GET"_method)(
331 [&](const crow::request& req, crow::response& res) {
332 res.json_value = {
333 {"@odata.context",
334 "/redfish/v1/$metadata#ManagerCollection.ManagerCollection"},
335 {"@odata.id", "/redfish/v1/Managers"},
336 {"@odata.type", "#ManagerCollection.ManagerCollection"},
337 {"Name", "Manager Collection"},
338 {"Members@odata.count", 1},
339 {"Members", {{{"@odata.id", "/redfish/v1/Managers/openbmc"}}}}};
340 res.end();
341 });
342
343 CROW_ROUTE(app, "/redfish/v1/Managers/openbmc/")
344 .methods(
345 "GET"_method)([&](const crow::request& req, crow::response& res) {
346 time_t t = time(NULL);
347 tm* mytime = std::localtime(&t);
348 if (mytime == nullptr) {
349 res.code = 500;
350 res.end();
351 return;
352 }
353 std::array<char, 100> time_buffer;
354 std::size_t len = std::strftime(time_buffer.data(), time_buffer.size(),
355 "%FT%TZ", mytime);
356 if (len == 0) {
357 res.code = 500;
358 res.end();
359 return;
360 }
361 res.json_value = {
362 {"@odata.context", "/redfish/v1/$metadata#Manager.Manager"},
363 {"@odata.id", "/redfish/v1/Managers/openbmc"},
364 {"@odata.type", "#Manager.v1_3_0.Manager"},
365 {"Id", "openbmc"},
366 {"Name", "OpenBmc Manager"},
367 {"Description", "Baseboard Management Controller"},
368 {"UUID",
369 app.template get_middleware<PersistentData::Middleware>()
370 .system_uuid},
371 {"Model", "OpenBmc"}, // TODO(ed), get model
372 {"DateTime", time_buffer.data()},
373 {"Status",
374 {{"State", "Enabled"}, {"Health", "OK"}, {"HealthRollup", "OK"}}},
375 {"FirmwareVersion", "1234456789"}, // TODO(ed) get fwversion
376 {"PowerState", "On"}};
377 get_redfish_sub_routes(app, "/redfish/v1/Managers/openbmc/",
378 res.json_value);
379 res.end();
380 });
381
382 CROW_ROUTE(app, "/redfish/v1/Managers/NetworkProtocol/")
383 .methods(
384 "GET"_method)([&](const crow::request& req, crow::response& res) {
385 std::array<char, HOST_NAME_MAX> hostname;
386 if (gethostname(hostname.data(), hostname.size()) != 0) {
387 res.code = 500;
388 res.end();
389 return;
390 }
391 res.json_value = {
392 {"@odata.context",
393 "/redfish/v1/"
394 "$metadata#ManagerNetworkProtocol.ManagerNetworkProtocol"},
395 {"@odata.id", "/redfish/v1/Managers/BMC/NetworkProtocol"},
396 {"@odata.type",
397 "#ManagerNetworkProtocol.v1_1_0.ManagerNetworkProtocol"},
398 {"Id", "NetworkProtocol"},
399 {"Name", "Manager Network Protocol"},
400 {"Description", "Manager Network Service"},
401 {"Status",
402 {{"State", "Enabled"}, {"Health", "OK"}, {"HealthRollup", "OK"}}},
403 {"HostName", hostname.data()}}; // TODO(ed) get hostname
404 std::string netstat_out = execute_process("netstat -tuln");
405
406 std::map<int, const char*> service_types{{22, "SSH"},
407 {443, "HTTPS"},
408 {1900, "SSDP"},
409 {623, "IPMI"},
410 {427, "SLP"}};
411
412 std::vector<std::string> lines;
413 boost::split(lines, netstat_out, boost::is_any_of("\n"));
414 auto lines_it = lines.begin();
415 lines_it++; // skip the netstat header
416 lines_it++;
417 while (lines_it != lines.end()) {
418 std::vector<std::string> columns;
419 boost::split(columns, *lines_it, boost::is_any_of("\t "),
420 boost::token_compress_on);
421 if (columns.size() >= 5) {
422 std::size_t found = columns[3].find_last_of(":");
423 if (found != std::string::npos) {
424 std::string port_str = columns[3].substr(found + 1);
425 int port = std::stoi(port_str.c_str());
426 auto type_it = service_types.find(port);
427 if (type_it != service_types.end()) {
Ed Tanous2a866f82017-10-25 17:46:24 -0700428 res.json_value[type_it->second] = {{"ProtocolEnabled", true},
429 {"Port", port}};
430 }
431 }
432 }
433 lines_it++;
434 }
435
436 get_redfish_sub_routes(app, "/redfish/v1/", res.json_value);
437 res.end();
438 });
Ed Tanous3dac7492017-08-02 13:46:20 -0700439}
Ed Tanous911ac312017-08-15 09:37:42 -0700440} // namespace redfish
441} // namespace crow