Václav Kubernát | babbab9 | 2021-01-27 09:25:05 +0100 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2021 CESNET, https://photonics.cesnet.cz/ |
| 3 | * |
| 4 | * Written by Václav Kubernát <kubernat@cesnet.cz> |
| 5 | * |
| 6 | */ |
| 7 | |
| 8 | #include <fmt/core.h> |
| 9 | #include <fstream> |
| 10 | #include <pwd.h> |
| 11 | #include <shadow.h> |
| 12 | #include <spdlog/spdlog.h> |
| 13 | #include <sstream> |
| 14 | #include "Authentication.h" |
| 15 | #include "system_vars.h" |
| 16 | #include "utils/exec.h" |
| 17 | #include "utils/io.h" |
| 18 | #include "utils/libyang.h" |
| 19 | #include "utils/sysrepo.h" |
| 20 | #include "utils/time.h" |
| 21 | |
| 22 | using namespace std::string_literals; |
| 23 | namespace { |
| 24 | const auto czechlight_system_module = "czechlight-system"s; |
| 25 | const auto authentication_container = "/" + czechlight_system_module + ":authentication"; |
| 26 | const auto change_password_action = "/" + czechlight_system_module + ":authentication/users/change-password"; |
| 27 | const auto add_key_action = "/" + czechlight_system_module + ":authentication/users/add-authorized-key"; |
| 28 | const auto remove_key_action = "/" + czechlight_system_module + ":authentication/users/authorized-keys/remove"; |
| 29 | } |
| 30 | |
| 31 | namespace velia::system { |
| 32 | namespace { |
| 33 | |
| 34 | void writeKeys(const std::string& filename, const std::vector<std::string>& keys) |
| 35 | { |
| 36 | std::ostringstream ss; |
| 37 | |
| 38 | for (const auto& key : keys) { |
| 39 | ss << key << "\n"; |
| 40 | } |
| 41 | utils::safeWriteFile(filename, ss.str()); |
| 42 | } |
| 43 | } |
| 44 | |
| 45 | namespace impl { |
Tomáš Pecka | d9e741f | 2021-02-10 15:51:17 +0100 | [diff] [blame] | 46 | void changePassword(const std::string& name, const std::string& password, const std::string& etc_shadow) |
Václav Kubernát | babbab9 | 2021-01-27 09:25:05 +0100 | [diff] [blame] | 47 | { |
| 48 | utils::execAndWait(spdlog::get("system"), CHPASSWD_EXECUTABLE, {}, name + ":" + password); |
Tomáš Pecka | d9e741f | 2021-02-10 15:51:17 +0100 | [diff] [blame] | 49 | auto shadow = velia::utils::readFileToString(etc_shadow); |
Václav Kubernát | babbab9 | 2021-01-27 09:25:05 +0100 | [diff] [blame] | 50 | utils::safeWriteFile(BACKUP_ETC_SHADOW_FILE, shadow); |
| 51 | } |
Jan Kundrát | b25ef24 | 2021-02-18 14:56:54 +0100 | [diff] [blame] | 52 | |
| 53 | auto file_open(const char* filename, const char* mode) |
| 54 | { |
| 55 | auto res = std::unique_ptr<std::FILE, decltype(&std::fclose)>(std::fopen(filename, mode), std::fclose); |
| 56 | if (!res.get()) { |
| 57 | throw std::system_error{errno, std::system_category(), "fopen("s + filename + ") failed"}; |
| 58 | } |
| 59 | return res; |
| 60 | } |
Václav Kubernát | babbab9 | 2021-01-27 09:25:05 +0100 | [diff] [blame] | 61 | } |
| 62 | |
| 63 | std::string Authentication::homeDirectory(const std::string& username) |
| 64 | { |
Jan Kundrát | b25ef24 | 2021-02-18 14:56:54 +0100 | [diff] [blame] | 65 | auto passwdFile = impl::file_open(m_etc_passwd.c_str(), "r"); |
Václav Kubernát | babbab9 | 2021-01-27 09:25:05 +0100 | [diff] [blame] | 66 | passwd entryBuf; |
| 67 | size_t bufLen = 10; |
| 68 | auto buffer = std::make_unique<char[]>(bufLen); |
| 69 | passwd* entry; |
| 70 | |
| 71 | while (true) { |
Václav Kubernát | 515cef0 | 2021-03-25 05:48:57 +0100 | [diff] [blame] | 72 | auto pos = ftell(passwdFile.get()); |
Jan Kundrát | b25ef24 | 2021-02-18 14:56:54 +0100 | [diff] [blame] | 73 | auto ret = fgetpwent_r(passwdFile.get(), &entryBuf, buffer.get(), bufLen, &entry); |
Václav Kubernát | babbab9 | 2021-01-27 09:25:05 +0100 | [diff] [blame] | 74 | if (ret == ERANGE) { |
| 75 | bufLen += 100; |
| 76 | buffer = std::make_unique<char[]>(bufLen); |
Václav Kubernát | 515cef0 | 2021-03-25 05:48:57 +0100 | [diff] [blame] | 77 | fseek(passwdFile.get(), pos, SEEK_SET); |
Václav Kubernát | babbab9 | 2021-01-27 09:25:05 +0100 | [diff] [blame] | 78 | continue; |
| 79 | } |
Jan Kundrát | b25ef24 | 2021-02-18 14:56:54 +0100 | [diff] [blame] | 80 | |
| 81 | if (ret == ENOENT) { |
| 82 | break; |
Václav Kubernát | babbab9 | 2021-01-27 09:25:05 +0100 | [diff] [blame] | 83 | } |
| 84 | |
Jan Kundrát | b25ef24 | 2021-02-18 14:56:54 +0100 | [diff] [blame] | 85 | if (ret != 0) { |
| 86 | throw std::system_error{ret, std::system_category(), "fgetpwent_r() failed"}; |
| 87 | } |
| 88 | |
| 89 | assert(entry); |
| 90 | |
| 91 | if (username != entry->pw_name) { |
| 92 | continue; |
| 93 | } |
| 94 | |
| 95 | return entry->pw_dir; |
Václav Kubernát | babbab9 | 2021-01-27 09:25:05 +0100 | [diff] [blame] | 96 | } |
| 97 | |
| 98 | throw std::runtime_error("User " + username + " doesn't exist"); |
| 99 | } |
| 100 | |
Václav Kubernát | 8ea630e | 2021-02-18 16:55:25 +0100 | [diff] [blame] | 101 | std::map<std::string, std::optional<std::string>> Authentication::lastPasswordChanges() |
Václav Kubernát | babbab9 | 2021-01-27 09:25:05 +0100 | [diff] [blame] | 102 | { |
Jan Kundrát | b25ef24 | 2021-02-18 14:56:54 +0100 | [diff] [blame] | 103 | auto shadowFile = impl::file_open(m_etc_shadow.c_str(), "r"); |
Václav Kubernát | babbab9 | 2021-01-27 09:25:05 +0100 | [diff] [blame] | 104 | spwd entryBuf; |
| 105 | size_t bufLen = 10; |
| 106 | auto buffer = std::make_unique<char[]>(bufLen); |
| 107 | spwd* entry; |
| 108 | |
Václav Kubernát | 8ea630e | 2021-02-18 16:55:25 +0100 | [diff] [blame] | 109 | std::map<std::string, std::optional<std::string>> res; |
Václav Kubernát | babbab9 | 2021-01-27 09:25:05 +0100 | [diff] [blame] | 110 | while (true) { |
Václav Kubernát | 515cef0 | 2021-03-25 05:48:57 +0100 | [diff] [blame] | 111 | auto pos = ftell(shadowFile.get()); |
Jan Kundrát | b25ef24 | 2021-02-18 14:56:54 +0100 | [diff] [blame] | 112 | auto ret = fgetspent_r(shadowFile.get(), &entryBuf, buffer.get(), bufLen, &entry); |
Václav Kubernát | babbab9 | 2021-01-27 09:25:05 +0100 | [diff] [blame] | 113 | if (ret == ERANGE) { |
| 114 | bufLen += 100; |
| 115 | buffer = std::make_unique<char[]>(bufLen); |
Václav Kubernát | 515cef0 | 2021-03-25 05:48:57 +0100 | [diff] [blame] | 116 | fseek(shadowFile.get(), pos, SEEK_SET); |
Václav Kubernát | babbab9 | 2021-01-27 09:25:05 +0100 | [diff] [blame] | 117 | continue; |
| 118 | } |
| 119 | |
Jan Kundrát | b25ef24 | 2021-02-18 14:56:54 +0100 | [diff] [blame] | 120 | if (ret == ENOENT) { |
| 121 | break; |
Václav Kubernát | babbab9 | 2021-01-27 09:25:05 +0100 | [diff] [blame] | 122 | } |
| 123 | |
Jan Kundrát | b25ef24 | 2021-02-18 14:56:54 +0100 | [diff] [blame] | 124 | if (ret != 0) { |
| 125 | throw std::system_error{ret, std::system_category(), "fgetspent_r() failed"}; |
| 126 | } |
| 127 | |
| 128 | assert(entry); |
| 129 | |
Jan Kundrát | b25ef24 | 2021-02-18 14:56:54 +0100 | [diff] [blame] | 130 | using namespace std::chrono_literals; |
| 131 | using TimeType = std::chrono::time_point<std::chrono::system_clock>; |
Václav Kubernát | 8ea630e | 2021-02-18 16:55:25 +0100 | [diff] [blame] | 132 | res.emplace(entry->sp_namp, velia::utils::yangTimeFormat(TimeType(24h * entry->sp_lstchg))); |
Václav Kubernát | babbab9 | 2021-01-27 09:25:05 +0100 | [diff] [blame] | 133 | } |
| 134 | |
Václav Kubernát | 8ea630e | 2021-02-18 16:55:25 +0100 | [diff] [blame] | 135 | return res; |
Václav Kubernát | babbab9 | 2021-01-27 09:25:05 +0100 | [diff] [blame] | 136 | } |
| 137 | |
| 138 | std::string Authentication::authorizedKeysPath(const std::string& username) |
| 139 | { |
| 140 | using namespace fmt::literals; |
Tomáš Pecka | 08c1aa4 | 2021-08-02 14:36:40 +0200 | [diff] [blame] | 141 | |
| 142 | #if FMT_VERSION >= 80000 // fmt >= 8.0.0 |
| 143 | auto str = fmt::runtime(m_authorized_keys_format); |
| 144 | #else |
| 145 | auto str = m_authorized_keys_format; |
| 146 | #endif |
| 147 | |
| 148 | return fmt::format(str, "USER"_a=username, "HOME"_a=homeDirectory(username)); |
Václav Kubernát | babbab9 | 2021-01-27 09:25:05 +0100 | [diff] [blame] | 149 | } |
| 150 | |
| 151 | std::vector<std::string> Authentication::listKeys(const std::string& username) |
| 152 | { |
| 153 | std::vector<std::string> res; |
| 154 | std::ifstream ifs(authorizedKeysPath(username)); |
| 155 | if (!ifs.is_open()) { |
| 156 | return res; |
| 157 | } |
| 158 | std::string line; |
| 159 | while (std::getline(ifs, line)) { |
| 160 | if (line.find_first_not_of(" \r\t") == std::string::npos) { |
| 161 | continue; |
| 162 | } |
| 163 | |
| 164 | res.emplace_back(line); |
| 165 | } |
| 166 | |
| 167 | return res; |
| 168 | } |
| 169 | |
| 170 | std::vector<User> Authentication::listUsers() |
| 171 | { |
| 172 | std::vector<User> res; |
Jan Kundrát | b25ef24 | 2021-02-18 14:56:54 +0100 | [diff] [blame] | 173 | auto passwdFile = impl::file_open(m_etc_passwd.c_str(), "r"); |
Václav Kubernát | babbab9 | 2021-01-27 09:25:05 +0100 | [diff] [blame] | 174 | passwd entryBuf; |
| 175 | size_t bufLen = 10; |
| 176 | auto buffer = std::make_unique<char[]>(bufLen); |
| 177 | passwd* entry; |
| 178 | |
Václav Kubernát | 8ea630e | 2021-02-18 16:55:25 +0100 | [diff] [blame] | 179 | auto pwChanges = lastPasswordChanges(); |
Václav Kubernát | babbab9 | 2021-01-27 09:25:05 +0100 | [diff] [blame] | 180 | while (true) { |
Václav Kubernát | 515cef0 | 2021-03-25 05:48:57 +0100 | [diff] [blame] | 181 | auto pos = ftell(passwdFile.get()); |
Jan Kundrát | b25ef24 | 2021-02-18 14:56:54 +0100 | [diff] [blame] | 182 | auto ret = fgetpwent_r(passwdFile.get(), &entryBuf, buffer.get(), bufLen, &entry); |
Václav Kubernát | babbab9 | 2021-01-27 09:25:05 +0100 | [diff] [blame] | 183 | if (ret == ERANGE) { |
| 184 | bufLen += 100; |
| 185 | buffer = std::make_unique<char[]>(bufLen); |
Václav Kubernát | 515cef0 | 2021-03-25 05:48:57 +0100 | [diff] [blame] | 186 | fseek(passwdFile.get(), pos, SEEK_SET); |
Václav Kubernát | babbab9 | 2021-01-27 09:25:05 +0100 | [diff] [blame] | 187 | continue; |
| 188 | } |
| 189 | |
| 190 | if (ret == ENOENT) { |
| 191 | break; |
| 192 | } |
Jan Kundrát | b25ef24 | 2021-02-18 14:56:54 +0100 | [diff] [blame] | 193 | |
| 194 | if (ret != 0) { |
| 195 | throw std::system_error{ret, std::system_category(), "fgetpwent_r() failed"}; |
| 196 | } |
| 197 | |
| 198 | assert(entry); |
Václav Kubernát | babbab9 | 2021-01-27 09:25:05 +0100 | [diff] [blame] | 199 | User user; |
| 200 | user.name = entry->pw_name; |
| 201 | user.authorizedKeys = listKeys(user.name); |
Václav Kubernát | 8ea630e | 2021-02-18 16:55:25 +0100 | [diff] [blame] | 202 | if (auto it = pwChanges.find(user.name); it != pwChanges.end()) { |
| 203 | user.lastPasswordChange = it->second; |
| 204 | } |
Václav Kubernát | babbab9 | 2021-01-27 09:25:05 +0100 | [diff] [blame] | 205 | res.emplace_back(user); |
| 206 | } |
| 207 | |
Václav Kubernát | babbab9 | 2021-01-27 09:25:05 +0100 | [diff] [blame] | 208 | return res; |
| 209 | } |
| 210 | |
| 211 | void Authentication::addKey(const std::string& username, const std::string& key) |
| 212 | { |
| 213 | try { |
| 214 | utils::execAndWait(spdlog::get("system"), SSH_KEYGEN_EXECUTABLE, {"-l", "-f", "-"}, key, {utils::ExecOptions::DropRoot}); |
| 215 | } catch (std::runtime_error& ex) { |
| 216 | using namespace fmt::literals; |
| 217 | throw AuthException(fmt::format("Key is not a valid SSH public key: {stderr}\n{key}", "stderr"_a=ex.what(), "key"_a=key)); |
| 218 | } |
| 219 | auto currentKeys = listKeys(username); |
| 220 | currentKeys.emplace_back(key); |
| 221 | writeKeys(authorizedKeysPath(username), currentKeys); |
| 222 | } |
| 223 | |
| 224 | void Authentication::removeKey(const std::string& username, const int index) |
| 225 | { |
| 226 | auto currentKeys = listKeys(username); |
| 227 | if (currentKeys.size() == 1) { |
| 228 | // FIXME: maybe add an option to bypass this check? |
| 229 | throw AuthException("Can't remove last key."); |
| 230 | } |
| 231 | currentKeys.erase(currentKeys.begin() + index); |
| 232 | writeKeys(authorizedKeysPath(username), currentKeys); |
| 233 | } |
| 234 | } |
| 235 | |
| 236 | void usersToTree(libyang::S_Context ctx, const std::vector<velia::system::User> users, libyang::S_Data_Node& out) |
| 237 | { |
| 238 | out = std::make_shared<libyang::Data_Node>( |
| 239 | ctx, |
| 240 | authentication_container.c_str(), |
| 241 | nullptr, |
| 242 | LYD_ANYDATA_CONSTSTRING, |
| 243 | 0); |
| 244 | for (const auto& user : users) { |
| 245 | auto userNode = out->new_path(ctx, ("users[name='" + user.name + "']").c_str(), nullptr, LYD_ANYDATA_CONSTSTRING, 0); |
| 246 | |
| 247 | decltype(user.authorizedKeys)::size_type entries = 0; |
| 248 | for (const auto& authorizedKey : user.authorizedKeys) { |
| 249 | auto entry = userNode->new_path(ctx, ("authorized-keys[index='" + std::to_string(entries) + "']").c_str(), nullptr, LYD_ANYDATA_CONSTSTRING, 0); |
| 250 | entry->new_path(ctx, "public-key", authorizedKey.c_str(), LYD_ANYDATA_CONSTSTRING, 0); |
| 251 | entries++; |
| 252 | } |
| 253 | |
| 254 | if (user.lastPasswordChange) { |
| 255 | userNode->new_path(ctx, "password-last-change", user.lastPasswordChange->c_str(), LYD_ANYDATA_CONSTSTRING, 0); |
| 256 | } |
| 257 | } |
| 258 | } |
| 259 | |
| 260 | velia::system::Authentication::Authentication( |
| 261 | sysrepo::S_Session srSess, |
| 262 | const std::string& etc_passwd, |
| 263 | const std::string& etc_shadow, |
| 264 | const std::string& authorized_keys_format, |
| 265 | ChangePassword changePassword |
| 266 | ) |
| 267 | : m_log(spdlog::get("system")) |
| 268 | , m_etc_passwd(etc_passwd) |
| 269 | , m_etc_shadow(etc_shadow) |
| 270 | , m_authorized_keys_format(authorized_keys_format) |
| 271 | , m_session(srSess) |
| 272 | , m_sub(std::make_shared<sysrepo::Subscribe>(srSess)) |
| 273 | { |
| 274 | m_log->debug("Initializing authentication"); |
| 275 | m_log->debug("Using {} as passwd file", m_etc_passwd); |
| 276 | m_log->debug("Using {} as shadow file", m_etc_shadow); |
| 277 | m_log->debug("Using {} authorized_keys format", m_authorized_keys_format); |
| 278 | utils::ensureModuleImplemented(srSess, "czechlight-system", "2021-01-13"); |
| 279 | |
| 280 | sysrepo::OperGetItemsCb listUsersCb = [this] ( |
Jan Kundrát | ef2b380 | 2021-02-18 09:57:05 +0100 | [diff] [blame] | 281 | auto session, |
| 282 | auto, |
| 283 | auto, |
| 284 | auto, |
| 285 | auto, |
| 286 | auto& out) { |
Václav Kubernát | babbab9 | 2021-01-27 09:25:05 +0100 | [diff] [blame] | 287 | m_log->debug("Listing users"); |
| 288 | |
| 289 | auto users = listUsers(); |
| 290 | m_log->trace("got {} users", users.size()); |
| 291 | usersToTree(session->get_context(), users, out); |
| 292 | |
| 293 | return SR_ERR_OK; |
| 294 | }; |
| 295 | |
| 296 | sysrepo::RpcTreeCb changePasswordCb = [this, changePassword] ( |
Jan Kundrát | ef2b380 | 2021-02-18 09:57:05 +0100 | [diff] [blame] | 297 | auto session, |
| 298 | auto, |
| 299 | auto input, |
| 300 | auto, |
| 301 | auto, |
| 302 | auto output) { |
Václav Kubernát | babbab9 | 2021-01-27 09:25:05 +0100 | [diff] [blame] | 303 | |
Tomáš Pecka | fd90efb | 2021-10-07 10:40:44 +0200 | [diff] [blame] | 304 | auto userNode = utils::getUniqueSubtree(input, (authentication_container + "/users" ).c_str()).value(); |
| 305 | auto name = utils::getValueAsString(utils::getUniqueSubtree(userNode, "name").value()); |
| 306 | auto password = utils::getValueAsString(utils::getUniqueSubtree(userNode, "change-password/password-cleartext").value()); |
Václav Kubernát | babbab9 | 2021-01-27 09:25:05 +0100 | [diff] [blame] | 307 | m_log->debug("Changing password for {}", name); |
| 308 | try { |
Tomáš Pecka | d9e741f | 2021-02-10 15:51:17 +0100 | [diff] [blame] | 309 | changePassword(name, password, m_etc_shadow); |
Václav Kubernát | babbab9 | 2021-01-27 09:25:05 +0100 | [diff] [blame] | 310 | output->new_path(session->get_context(), "result", "success", LYD_ANYDATA_CONSTSTRING, LYD_PATH_OPT_OUTPUT); |
| 311 | m_log->info("Changed password for {}", name); |
| 312 | } catch (std::runtime_error& ex) { |
| 313 | output->new_path(session->get_context(), "result", "failure", LYD_ANYDATA_CONSTSTRING, LYD_PATH_OPT_OUTPUT); |
| 314 | output->new_path(session->get_context(), "message", ex.what(), LYD_ANYDATA_CONSTSTRING, LYD_PATH_OPT_OUTPUT); |
| 315 | m_log->info("Failed to change password for {}: {}", name, ex.what()); |
| 316 | } |
| 317 | |
| 318 | return SR_ERR_OK; |
| 319 | }; |
| 320 | |
| 321 | sysrepo::RpcTreeCb addKeyCb = [this] ( |
Jan Kundrát | ef2b380 | 2021-02-18 09:57:05 +0100 | [diff] [blame] | 322 | auto session, |
| 323 | auto, |
| 324 | auto input, |
| 325 | auto, |
| 326 | auto, |
| 327 | auto output) { |
Václav Kubernát | babbab9 | 2021-01-27 09:25:05 +0100 | [diff] [blame] | 328 | |
Tomáš Pecka | fd90efb | 2021-10-07 10:40:44 +0200 | [diff] [blame] | 329 | auto userNode = utils::getUniqueSubtree(input, (authentication_container + "/users").c_str()).value(); |
| 330 | auto name = utils::getValueAsString(utils::getUniqueSubtree(userNode, "name").value()); |
| 331 | auto key = utils::getValueAsString(utils::getUniqueSubtree(userNode, "add-authorized-key/key").value()); |
Václav Kubernát | babbab9 | 2021-01-27 09:25:05 +0100 | [diff] [blame] | 332 | m_log->debug("Adding key for {}", name); |
| 333 | try { |
| 334 | addKey(name, key); |
| 335 | output->new_path(session->get_context(), "result", "success", LYD_ANYDATA_CONSTSTRING, LYD_PATH_OPT_OUTPUT); |
| 336 | m_log->info("Added a key for {}", name); |
| 337 | } catch (AuthException& ex) { |
| 338 | output->new_path(session->get_context(), "result", "failure", LYD_ANYDATA_CONSTSTRING, LYD_PATH_OPT_OUTPUT); |
| 339 | output->new_path(session->get_context(), "message", ex.what(), LYD_ANYDATA_CONSTSTRING, LYD_PATH_OPT_OUTPUT); |
| 340 | m_log->warn("Failed to add a key for {}: {}", name, ex.what()); |
| 341 | } |
| 342 | |
| 343 | return SR_ERR_OK; |
| 344 | }; |
| 345 | |
| 346 | sysrepo::RpcTreeCb removeKeyCb = [this] ( |
Jan Kundrát | ef2b380 | 2021-02-18 09:57:05 +0100 | [diff] [blame] | 347 | auto session, |
| 348 | auto, |
| 349 | auto input, |
| 350 | auto, |
| 351 | auto, |
| 352 | auto output) { |
Václav Kubernát | babbab9 | 2021-01-27 09:25:05 +0100 | [diff] [blame] | 353 | |
Tomáš Pecka | fd90efb | 2021-10-07 10:40:44 +0200 | [diff] [blame] | 354 | auto userNode = utils::getUniqueSubtree(input, (authentication_container + "/users").c_str()).value(); |
| 355 | auto name = utils::getValueAsString(utils::getUniqueSubtree(userNode, "name").value()); |
| 356 | auto key = std::stol(utils::getValueAsString(utils::getUniqueSubtree(userNode, "authorized-keys/index").value())); |
Václav Kubernát | babbab9 | 2021-01-27 09:25:05 +0100 | [diff] [blame] | 357 | m_log->debug("Removing key for {}", name); |
| 358 | try { |
| 359 | removeKey(name, key); |
| 360 | output->new_path(session->get_context(), "result", "success", LYD_ANYDATA_CONSTSTRING, LYD_PATH_OPT_OUTPUT); |
| 361 | m_log->info("Removed key for {}", name); |
| 362 | } catch (AuthException& ex) { |
| 363 | output->new_path(session->get_context(), "result", "failure", LYD_ANYDATA_CONSTSTRING, LYD_PATH_OPT_OUTPUT); |
| 364 | output->new_path(session->get_context(), "message", ex.what(), LYD_ANYDATA_CONSTSTRING, LYD_PATH_OPT_OUTPUT); |
| 365 | m_log->warn("Failed to remove a key for {}: {}", name, ex.what()); |
| 366 | } |
| 367 | |
| 368 | return SR_ERR_OK; |
| 369 | }; |
| 370 | |
| 371 | m_sub->oper_get_items_subscribe(czechlight_system_module.c_str(), listUsersCb, authentication_container.c_str()); |
| 372 | m_sub->rpc_subscribe_tree(change_password_action.c_str(), changePasswordCb); |
| 373 | m_sub->rpc_subscribe_tree(add_key_action.c_str(), addKeyCb); |
| 374 | m_sub->rpc_subscribe_tree(remove_key_action.c_str(), removeKeyCb); |
| 375 | } |