Loading src/admin.cpp +84 −1 Changes for src/admin.cpp: 84 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -50,9 +50,22 @@ #include "session.h" #include "export.h" #include "import.h" #include "krb5.h" namespace authdb { /* Hex-encode raw bytes to a lowercase hex string */ static std::string hexEncode(const std::vector<uint8_t> &data) { static const char hex[] = "0123456789abcdef"; std::string out; out.reserve(data.size() * 2); for (uint8_t b : data) { out.push_back(hex[b >> 4]); out.push_back(hex[b & 0x0F]); } return out; } static void sendJson(libhttppp::HttpRequest &curreq, json_object *jobj) { const char *str = json_object_to_json_string_ext(jobj, JSON_C_TO_STRING_PLAIN); size_t len = strlen(str); Loading Loading @@ -203,6 +216,8 @@ namespace authdb { uid.generate(); class UserData udat(uid); bool username = false; std::string capturedRawPw; std::string capturedUsername; for (const auto &curformdat : curform.multipartData()) { if (curformdat.value.empty()) continue; Loading Loading @@ -242,11 +257,15 @@ namespace authdb { udat.append(cudat); delete[] cudat.data; } else if (cudat.storage == TextStorage) { if (strcmp(cudat.fieldname, "username") == 0) username = true; if (strcmp(cudat.fieldname, "username") == 0) { username = true; capturedUsername = std::string(curformdat.value.begin(), curformdat.value.end()); } if (strcmp(cudat.fieldname, "pwhash") == 0) { Hash hash; std::string pwhash, inpw; std::copy(curformdat.value.begin(), curformdat.value.end(), std::back_inserter(inpw)); capturedRawPw = inpw; hash.hash(inpw, pwhash); cudat.datasize = pwhash.length() + 1; cudat.data = new char[cudat.datasize]; Loading Loading @@ -277,6 +296,33 @@ namespace authdb { if (!username) throw AuthBackendError("username not set!"); /* * Compute Kerberos long-term key (RFC 3962 string-to-key) from * the raw password so that MIT / Heimdal kinit can authenticate. * Salt = REALM + username, where REALM = upper-case domain name. */ if (!capturedRawPw.empty() && !capturedUsername.empty() && strcmp(cdid, "admin") != 0) { try { uuid::uuid did; did.parse(cdid); class DomainData ddat(did); Domain dom; size_t dpos = sizeof(AuthHeader); dom.info(_AdminBackend, ddat, dpos); std::string domainName = ddat.getDomainName(); std::string realm = domainName; std::transform(realm.begin(), realm.end(), realm.begin(), [](unsigned char c){ return std::toupper(c); }); std::string salt = realm + capturedUsername; auto krbKeyBytes = Krb5Crypto::string2key( capturedRawPw, salt, KRB5_ENCTYPE_AES256_CTS_HMAC_SHA1); udat.setKrbKey(hexEncode(krbKeyBytes)); } catch (...) { /* Non-fatal: user is created without krbkey */ } } User user; user.create(*backend, &udat); Loading Loading @@ -315,6 +361,7 @@ namespace authdb { std::shared_ptr<authdb::AuthData> editrec = std::make_shared<authdb::AuthData>(uid); bool data = false; std::string editRawPw; libhttppp::HttpForm curform; curform.parse(curreq); Loading Loading @@ -356,6 +403,7 @@ namespace authdb { sha512 hash; std::string pwhash, inpw; inpw.assign(curformdat.value.begin(), curformdat.value.end()); editRawPw = inpw; hash.hash(inpw, pwhash); curec->datasize = pwhash.length() + 1; curec->data = new char[curec->datasize]; Loading Loading @@ -394,6 +442,41 @@ namespace authdb { editRecord(*backend, *editrec, UserData); } /* * If the password was changed, recompute the Kerberos key. * Look up the username from the existing user record. */ if (!editRawPw.empty() && strcmp(cdid, "admin") != 0) { try { User user; class UserData udat(uid); size_t upos = sizeof(AuthHeader); user.info(*backend, udat, upos); std::string uname = udat.getUsername(); if (!uname.empty()) { uuid::uuid did; did.parse(cdid); class DomainData ddat(did); Domain dom; size_t dpos = sizeof(AuthHeader); dom.info(_AdminBackend, ddat, dpos); std::string domainName = ddat.getDomainName(); std::string realm = domainName; std::transform(realm.begin(), realm.end(), realm.begin(), [](unsigned char c){ return std::toupper(c); }); std::string salt = realm + uname; auto krbKeyBytes = Krb5Crypto::string2key( editRawPw, salt, KRB5_ENCTYPE_AES256_CTS_HMAC_SHA1); udat.setKrbKey(hexEncode(krbKeyBytes)); upos = sizeof(AuthHeader); user.edit(*backend, udat, upos); } } catch (...) { /* Non-fatal */ } } json_object *jobj = json_object_new_object(); json_object_object_add(jobj, "status", json_object_new_string("ok")); sendJson(curreq, jobj); Loading src/api.cpp +41 −0 Changes for src/api.cpp: 41 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -44,6 +44,8 @@ #include "client.h" #include "gpo.h" #include "session.h" #include "domain.h" #include "krb5.h" namespace authdb { Loading @@ -58,6 +60,18 @@ namespace { return diff == 0; } // Hex-encode raw bytes to a lowercase hex string std::string hexEncode(const std::vector<uint8_t> &data) { static const char hex[] = "0123456789abcdef"; std::string out; out.reserve(data.size() * 2); for (uint8_t b : data) { out.push_back(hex[b >> 4]); out.push_back(hex[b & 0x0F]); } return out; } // Helper: get required top-level "commands" array and type-check it json_object* get_commands_array_or_throw(json_object* request) { json_object* cmd = json_object_object_get(request, "commands"); Loading Loading @@ -557,6 +571,33 @@ public: } udat.setPwHash(newhash.c_str()); /* * Recompute Kerberos long-term key from raw new password. * Salt = REALM + username, REALM = upper-case domain name. */ try { DomainBackend dbackend(_Backend); uuid::uuid did_val = _Did; class DomainData ddat(did_val); Domain dom; size_t dpos = sizeof(AuthHeader); dom.info(_Backend, ddat, dpos); std::string domainName = ddat.getDomainName(); std::string realm = domainName; std::transform(realm.begin(), realm.end(), realm.begin(), [](unsigned char c){ return std::toupper(c); }); std::string uname = udat.getUsername(); if (!uname.empty()) { std::string salt = realm + uname; auto krbKeyBytes = Krb5Crypto::string2key( newpw, salt, KRB5_ENCTYPE_AES256_CTS_HMAC_SHA1); udat.setKrbKey(hexEncode(krbKeyBytes)); } } catch (...) { /* Non-fatal: krbkey update failed */ } ustart = sizeof(AuthHeader); user.edit(_Backend, udat, ustart); Loading src/krb5.cpp +51 −5 Changes for src/krb5.cpp: 51 added lines, 5 removed lines. Original line number Diff line number Diff line Loading @@ -66,6 +66,27 @@ namespace { return r; } /* Hex-decode a string of hex characters into raw bytes */ std::vector<uint8_t> hexDecode(const std::string &hex) { std::vector<uint8_t> out; out.reserve(hex.size() / 2); for (size_t i = 0; i + 1 < hex.size(); i += 2) { unsigned int byte = 0; auto nibble = [](char c) -> int { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'f') return 10 + c - 'a'; if (c >= 'A' && c <= 'F') return 10 + c - 'A'; return -1; }; int hi = nibble(hex[i]); int lo = nibble(hex[i + 1]); if (hi < 0 || lo < 0) break; byte = (static_cast<unsigned>(hi) << 4) | static_cast<unsigned>(lo); out.push_back(static_cast<uint8_t>(byte)); } return out; } /* GCD for n-fold */ size_t gcd(size_t a, size_t b) { while (b) { size_t t = b; b = a % b; a = t; } Loading Loading @@ -2872,6 +2893,7 @@ std::vector<uint8_t> Krb5KDC::handleStandardAsReq(const uint8_t *data, size_t le /* padata is stored as a SEQUENCE OF PA-DATA. * Parse it to find PA-ENC-TIMESTAMP (type 2). */ bool paVerified = false; bool foundPaEncTimestamp = false; size_t paOff = 0; uint8_t paTag; const uint8_t *paContent; Loading Loading @@ -2920,6 +2942,7 @@ std::vector<uint8_t> Krb5KDC::handleStandardAsReq(const uint8_t *data, size_t le } if (pdType == KRB5_PADATA_ENC_TIMESTAMP && !pdValue.empty()) { foundPaEncTimestamp = true; try { auto pa_plain = Krb5Crypto::decrypt(client_key, pdValue, KRB5_KU_AS_REQ_PA_ENC_TS); Loading @@ -2940,12 +2963,21 @@ std::vector<uint8_t> Krb5KDC::handleStandardAsReq(const uint8_t *data, size_t le } if (!paVerified) { if (foundPaEncTimestamp) { /* * PA-ENC-TIMESTAMP was present but decryption/verification * failed → wrong password. Return PREAUTH_FAILED so MIT * kinit reports an error instead of looping. */ return buildStandardError(KDC_ERR_PREAUTH_FAILED, "Pre-authentication failed", req.cname.realm, "krbtgt"); } /* * Return PREAUTH_REQUIRED (not PREAUTH_FAILED) with ETYPE-INFO2. * MIT kinit sends the first AS-REQ speculatively (possibly with * PA-FX-FAST or a wrong PA-ENC-TIMESTAMP). The KDC must always * reply with ETYPE-INFO2 so the client learns the correct salt * and enctype before retrying. * No PA-ENC-TIMESTAMP found at all (first speculative AS-REQ * or client sent only PA-FX-FAST). Return PREAUTH_REQUIRED * with ETYPE-INFO2 so the client learns salt and enctype. */ std::string salt = req.cname.realm + req.cname.name; auto etypeInfo = Asn1Der::encodeEtypeInfo2(selectedEtype, salt); Loading Loading @@ -3460,10 +3492,24 @@ bool Krb5KDC::lookupUser(AuthBackend &backend, const Krb5Principal &principal, size_t upos = sizeof(AuthHeader); user.info(backend, udat, upos); /* * Prefer stored Kerberos key (computed from raw password * at creation / password-change time). This is the only * way to be compatible with MIT / Heimdal kinit, because * RFC 3962 string-to-key must use the raw password. * * Fall back to deriving from the pwhash for backward * compatibility with users created before krbkey support. */ std::string krbkeyHex = udat.getKrbKey(); if (!krbkeyHex.empty()) { key = hexDecode(krbkeyHex); } else { std::string pwhash = udat.getPwHash(); std::string salt = principal.realm + principal.name; key = Krb5Crypto::string2key(pwhash, salt, KRB5_ENCTYPE_AES256_CTS_HMAC_SHA1); } return true; } } Loading src/user.cpp +18 −0 Changes for src/user.cpp: 18 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -41,6 +41,7 @@ authdb::UserData::UserData(const uuid::uuid &id) : AuthData(id){ lastname=nullptr; mail=nullptr; pwhash=nullptr; krbkey=nullptr; avatar=nullptr; Data.type=DataType::UserData; } Loading Loading @@ -119,6 +120,23 @@ const std::string authdb::UserData::getPwHash(){ } } void authdb::UserData::setKrbKey(const std::string &src){ setAuthData<char>(DataType::UserData,StorageType::TextStorage,this,&krbkey,"krbkey",src.c_str(),src.length()+1); } const std::string authdb::UserData::getKrbKey(){ try{ const char *res=getAuthData<char>(DataType::UserData,StorageType::TextStorage,this,&krbkey,"krbkey"); if(res) return res; else return ""; }catch(AuthBackendError &e){ std::cerr << e.what() << std::endl; return ""; } } void authdb::UserData::setUserName(const std::string &src){ setAuthData<char>(DataType::UserData,StorageType::TextStorage,this,&username,"username",src.c_str(),src.length()+1); Loading src/user.h +4 −0 Changes for src/user.h: 4 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -63,6 +63,9 @@ namespace authdb { void setPwHash(const std::string &src); const std::string getPwHash(); void setKrbKey(const std::string &src); const std::string getKrbKey(); void setAvatar(const std::vector<char> &src); void getAvatar(std::vector<char> &dest); Loading @@ -72,6 +75,7 @@ namespace authdb { AuthData::Record* lastname; AuthData::Record* mail; AuthData::Record* pwhash; AuthData::Record* krbkey; //optional AuthData::Record* avatar; friend class User; Loading Loading
src/admin.cpp +84 −1 Changes for src/admin.cpp: 84 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -50,9 +50,22 @@ #include "session.h" #include "export.h" #include "import.h" #include "krb5.h" namespace authdb { /* Hex-encode raw bytes to a lowercase hex string */ static std::string hexEncode(const std::vector<uint8_t> &data) { static const char hex[] = "0123456789abcdef"; std::string out; out.reserve(data.size() * 2); for (uint8_t b : data) { out.push_back(hex[b >> 4]); out.push_back(hex[b & 0x0F]); } return out; } static void sendJson(libhttppp::HttpRequest &curreq, json_object *jobj) { const char *str = json_object_to_json_string_ext(jobj, JSON_C_TO_STRING_PLAIN); size_t len = strlen(str); Loading Loading @@ -203,6 +216,8 @@ namespace authdb { uid.generate(); class UserData udat(uid); bool username = false; std::string capturedRawPw; std::string capturedUsername; for (const auto &curformdat : curform.multipartData()) { if (curformdat.value.empty()) continue; Loading Loading @@ -242,11 +257,15 @@ namespace authdb { udat.append(cudat); delete[] cudat.data; } else if (cudat.storage == TextStorage) { if (strcmp(cudat.fieldname, "username") == 0) username = true; if (strcmp(cudat.fieldname, "username") == 0) { username = true; capturedUsername = std::string(curformdat.value.begin(), curformdat.value.end()); } if (strcmp(cudat.fieldname, "pwhash") == 0) { Hash hash; std::string pwhash, inpw; std::copy(curformdat.value.begin(), curformdat.value.end(), std::back_inserter(inpw)); capturedRawPw = inpw; hash.hash(inpw, pwhash); cudat.datasize = pwhash.length() + 1; cudat.data = new char[cudat.datasize]; Loading Loading @@ -277,6 +296,33 @@ namespace authdb { if (!username) throw AuthBackendError("username not set!"); /* * Compute Kerberos long-term key (RFC 3962 string-to-key) from * the raw password so that MIT / Heimdal kinit can authenticate. * Salt = REALM + username, where REALM = upper-case domain name. */ if (!capturedRawPw.empty() && !capturedUsername.empty() && strcmp(cdid, "admin") != 0) { try { uuid::uuid did; did.parse(cdid); class DomainData ddat(did); Domain dom; size_t dpos = sizeof(AuthHeader); dom.info(_AdminBackend, ddat, dpos); std::string domainName = ddat.getDomainName(); std::string realm = domainName; std::transform(realm.begin(), realm.end(), realm.begin(), [](unsigned char c){ return std::toupper(c); }); std::string salt = realm + capturedUsername; auto krbKeyBytes = Krb5Crypto::string2key( capturedRawPw, salt, KRB5_ENCTYPE_AES256_CTS_HMAC_SHA1); udat.setKrbKey(hexEncode(krbKeyBytes)); } catch (...) { /* Non-fatal: user is created without krbkey */ } } User user; user.create(*backend, &udat); Loading Loading @@ -315,6 +361,7 @@ namespace authdb { std::shared_ptr<authdb::AuthData> editrec = std::make_shared<authdb::AuthData>(uid); bool data = false; std::string editRawPw; libhttppp::HttpForm curform; curform.parse(curreq); Loading Loading @@ -356,6 +403,7 @@ namespace authdb { sha512 hash; std::string pwhash, inpw; inpw.assign(curformdat.value.begin(), curformdat.value.end()); editRawPw = inpw; hash.hash(inpw, pwhash); curec->datasize = pwhash.length() + 1; curec->data = new char[curec->datasize]; Loading Loading @@ -394,6 +442,41 @@ namespace authdb { editRecord(*backend, *editrec, UserData); } /* * If the password was changed, recompute the Kerberos key. * Look up the username from the existing user record. */ if (!editRawPw.empty() && strcmp(cdid, "admin") != 0) { try { User user; class UserData udat(uid); size_t upos = sizeof(AuthHeader); user.info(*backend, udat, upos); std::string uname = udat.getUsername(); if (!uname.empty()) { uuid::uuid did; did.parse(cdid); class DomainData ddat(did); Domain dom; size_t dpos = sizeof(AuthHeader); dom.info(_AdminBackend, ddat, dpos); std::string domainName = ddat.getDomainName(); std::string realm = domainName; std::transform(realm.begin(), realm.end(), realm.begin(), [](unsigned char c){ return std::toupper(c); }); std::string salt = realm + uname; auto krbKeyBytes = Krb5Crypto::string2key( editRawPw, salt, KRB5_ENCTYPE_AES256_CTS_HMAC_SHA1); udat.setKrbKey(hexEncode(krbKeyBytes)); upos = sizeof(AuthHeader); user.edit(*backend, udat, upos); } } catch (...) { /* Non-fatal */ } } json_object *jobj = json_object_new_object(); json_object_object_add(jobj, "status", json_object_new_string("ok")); sendJson(curreq, jobj); Loading
src/api.cpp +41 −0 Changes for src/api.cpp: 41 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -44,6 +44,8 @@ #include "client.h" #include "gpo.h" #include "session.h" #include "domain.h" #include "krb5.h" namespace authdb { Loading @@ -58,6 +60,18 @@ namespace { return diff == 0; } // Hex-encode raw bytes to a lowercase hex string std::string hexEncode(const std::vector<uint8_t> &data) { static const char hex[] = "0123456789abcdef"; std::string out; out.reserve(data.size() * 2); for (uint8_t b : data) { out.push_back(hex[b >> 4]); out.push_back(hex[b & 0x0F]); } return out; } // Helper: get required top-level "commands" array and type-check it json_object* get_commands_array_or_throw(json_object* request) { json_object* cmd = json_object_object_get(request, "commands"); Loading Loading @@ -557,6 +571,33 @@ public: } udat.setPwHash(newhash.c_str()); /* * Recompute Kerberos long-term key from raw new password. * Salt = REALM + username, REALM = upper-case domain name. */ try { DomainBackend dbackend(_Backend); uuid::uuid did_val = _Did; class DomainData ddat(did_val); Domain dom; size_t dpos = sizeof(AuthHeader); dom.info(_Backend, ddat, dpos); std::string domainName = ddat.getDomainName(); std::string realm = domainName; std::transform(realm.begin(), realm.end(), realm.begin(), [](unsigned char c){ return std::toupper(c); }); std::string uname = udat.getUsername(); if (!uname.empty()) { std::string salt = realm + uname; auto krbKeyBytes = Krb5Crypto::string2key( newpw, salt, KRB5_ENCTYPE_AES256_CTS_HMAC_SHA1); udat.setKrbKey(hexEncode(krbKeyBytes)); } } catch (...) { /* Non-fatal: krbkey update failed */ } ustart = sizeof(AuthHeader); user.edit(_Backend, udat, ustart); Loading
src/krb5.cpp +51 −5 Changes for src/krb5.cpp: 51 added lines, 5 removed lines. Original line number Diff line number Diff line Loading @@ -66,6 +66,27 @@ namespace { return r; } /* Hex-decode a string of hex characters into raw bytes */ std::vector<uint8_t> hexDecode(const std::string &hex) { std::vector<uint8_t> out; out.reserve(hex.size() / 2); for (size_t i = 0; i + 1 < hex.size(); i += 2) { unsigned int byte = 0; auto nibble = [](char c) -> int { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'f') return 10 + c - 'a'; if (c >= 'A' && c <= 'F') return 10 + c - 'A'; return -1; }; int hi = nibble(hex[i]); int lo = nibble(hex[i + 1]); if (hi < 0 || lo < 0) break; byte = (static_cast<unsigned>(hi) << 4) | static_cast<unsigned>(lo); out.push_back(static_cast<uint8_t>(byte)); } return out; } /* GCD for n-fold */ size_t gcd(size_t a, size_t b) { while (b) { size_t t = b; b = a % b; a = t; } Loading Loading @@ -2872,6 +2893,7 @@ std::vector<uint8_t> Krb5KDC::handleStandardAsReq(const uint8_t *data, size_t le /* padata is stored as a SEQUENCE OF PA-DATA. * Parse it to find PA-ENC-TIMESTAMP (type 2). */ bool paVerified = false; bool foundPaEncTimestamp = false; size_t paOff = 0; uint8_t paTag; const uint8_t *paContent; Loading Loading @@ -2920,6 +2942,7 @@ std::vector<uint8_t> Krb5KDC::handleStandardAsReq(const uint8_t *data, size_t le } if (pdType == KRB5_PADATA_ENC_TIMESTAMP && !pdValue.empty()) { foundPaEncTimestamp = true; try { auto pa_plain = Krb5Crypto::decrypt(client_key, pdValue, KRB5_KU_AS_REQ_PA_ENC_TS); Loading @@ -2940,12 +2963,21 @@ std::vector<uint8_t> Krb5KDC::handleStandardAsReq(const uint8_t *data, size_t le } if (!paVerified) { if (foundPaEncTimestamp) { /* * PA-ENC-TIMESTAMP was present but decryption/verification * failed → wrong password. Return PREAUTH_FAILED so MIT * kinit reports an error instead of looping. */ return buildStandardError(KDC_ERR_PREAUTH_FAILED, "Pre-authentication failed", req.cname.realm, "krbtgt"); } /* * Return PREAUTH_REQUIRED (not PREAUTH_FAILED) with ETYPE-INFO2. * MIT kinit sends the first AS-REQ speculatively (possibly with * PA-FX-FAST or a wrong PA-ENC-TIMESTAMP). The KDC must always * reply with ETYPE-INFO2 so the client learns the correct salt * and enctype before retrying. * No PA-ENC-TIMESTAMP found at all (first speculative AS-REQ * or client sent only PA-FX-FAST). Return PREAUTH_REQUIRED * with ETYPE-INFO2 so the client learns salt and enctype. */ std::string salt = req.cname.realm + req.cname.name; auto etypeInfo = Asn1Der::encodeEtypeInfo2(selectedEtype, salt); Loading Loading @@ -3460,10 +3492,24 @@ bool Krb5KDC::lookupUser(AuthBackend &backend, const Krb5Principal &principal, size_t upos = sizeof(AuthHeader); user.info(backend, udat, upos); /* * Prefer stored Kerberos key (computed from raw password * at creation / password-change time). This is the only * way to be compatible with MIT / Heimdal kinit, because * RFC 3962 string-to-key must use the raw password. * * Fall back to deriving from the pwhash for backward * compatibility with users created before krbkey support. */ std::string krbkeyHex = udat.getKrbKey(); if (!krbkeyHex.empty()) { key = hexDecode(krbkeyHex); } else { std::string pwhash = udat.getPwHash(); std::string salt = principal.realm + principal.name; key = Krb5Crypto::string2key(pwhash, salt, KRB5_ENCTYPE_AES256_CTS_HMAC_SHA1); } return true; } } Loading
src/user.cpp +18 −0 Changes for src/user.cpp: 18 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -41,6 +41,7 @@ authdb::UserData::UserData(const uuid::uuid &id) : AuthData(id){ lastname=nullptr; mail=nullptr; pwhash=nullptr; krbkey=nullptr; avatar=nullptr; Data.type=DataType::UserData; } Loading Loading @@ -119,6 +120,23 @@ const std::string authdb::UserData::getPwHash(){ } } void authdb::UserData::setKrbKey(const std::string &src){ setAuthData<char>(DataType::UserData,StorageType::TextStorage,this,&krbkey,"krbkey",src.c_str(),src.length()+1); } const std::string authdb::UserData::getKrbKey(){ try{ const char *res=getAuthData<char>(DataType::UserData,StorageType::TextStorage,this,&krbkey,"krbkey"); if(res) return res; else return ""; }catch(AuthBackendError &e){ std::cerr << e.what() << std::endl; return ""; } } void authdb::UserData::setUserName(const std::string &src){ setAuthData<char>(DataType::UserData,StorageType::TextStorage,this,&username,"username",src.c_str(),src.length()+1); Loading
src/user.h +4 −0 Changes for src/user.h: 4 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -63,6 +63,9 @@ namespace authdb { void setPwHash(const std::string &src); const std::string getPwHash(); void setKrbKey(const std::string &src); const std::string getKrbKey(); void setAvatar(const std::vector<char> &src); void getAvatar(std::vector<char> &dest); Loading @@ -72,6 +75,7 @@ namespace authdb { AuthData::Record* lastname; AuthData::Record* mail; AuthData::Record* pwhash; AuthData::Record* krbkey; //optional AuthData::Record* avatar; friend class User; Loading