/******************************************************************************* * Copyright (c) 2025, Jan Koester jan.koester@gmx.net * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include "backend.h" #include "authdb.h" #include "user.h" #include "domain.h" #include "types.h" #include "hash.h" namespace authdb { bool initalized=true; class AuthDB : public libhttppp::HttpEvent{ public: AuthDB(authdb::AuthBackend &backend,netplus::socket *ssock) : HttpEvent(ssock), _Backend(backend){ _Indexpage.loadFile(_IndexElement,"../data/index.html"); std::ifstream authfs("../data/authdb.js", std::ios::ate); size_t asize = authfs.tellg(); _AuthJs.resize(asize); authfs.seekg(0); authfs.read(_AuthJs.data(),asize); }; void listUsers(libhttppp::HttpRequest *curreq, const int tid, ULONG_PTR args){ size_t rd=sizeof(authdb::AuthHeader),end=_Backend.end(); libhtmlpp::HtmlString userlist,out,dform; libhtmlpp::HtmlElement root=_IndexElement; libhtmlpp::HtmlElement *content=root.getElementbyID("content"); libhtmlpp::HtmlElement *domain=root.getElementbyID("domain"); User user; userlist <<"

UserList:

"; while(rd" << ""; }catch(AuthBackendError &e){ std::cerr << e.what() << std::endl; } } } dform << "" << "" << "" << ""; domain->insertChild(dform.parse()); userlist << "
" << udat.getUsername() << "
\"avatar\"" << "
  • " << "uid: " << cruid << "
  • " << "
  • Firstname:" << udat.getFirstname() << "
  • " << "
  • Lastname:" << udat.getLastname() << "
  • " << "
  • Email:" << udat.getMail() << "
  • " << "
"; content->insertChild(userlist.parse()); libhtmlpp::print(&root,out,true); libhttppp::HttpResponse rep; rep.setContentType("text/html"); rep.send(curreq,out.c_str(),out.size()); } void createUser(libhttppp::HttpRequest *curreq, const int tid, ULONG_PTR args){ libhttppp::HttpResponse rep; libhttppp::HttpForm curform; curform.parse(curreq); if (curform.getBoundary()) { uuid_t uid; uuid_generate(uid); class UserData udat(uid); for (libhttppp::HttpForm::MultipartForm::Data* curformdat = curform.MultipartFormData.getFormData(); curformdat; curformdat = curformdat->nextData()) { if(curformdat->Value.empty()) continue; std::string name; int stortype=TextStorage; for(libhttppp::HttpForm::MultipartForm::Data::ContentDisposition *curdispo=curformdat->getDisposition(); curdispo; curdispo=curdispo->nextContentDisposition() ){ std::string tmp=curdispo->getKey(); std::transform(tmp.begin(), tmp.end(), tmp.begin(), [](unsigned char c){ return std::tolower(c); }); if(tmp=="name") name=curdispo->getValue(); if(tmp=="filename"){ stortype=BinaryStorage; } } if(name.empty() || stortype == EmptyStorage) continue; struct AuthData::Record cudat; memset(&cudat,0,sizeof(AuthData::Record)); cudat.start=0xFE; uuid_copy(cudat.uuid,uid); cudat.type=UserData; cudat.storage=stortype; strcpy(cudat.fieldname,name.c_str()); if(cudat.storage==BinaryStorage){ cudat.datasize=curformdat->Value.size(); cudat.data=new char[cudat.datasize]; memcpy(cudat.data,curformdat->Value.data(), cudat.datasize); }else{ if(strcmp(cudat.fieldname,"pwhash")==0){ Hash hash; std::string pwhash,inpw; std::copy(curformdat->Value.begin(),curformdat->Value.end(), std::back_inserter(inpw)); hash.hash(inpw,pwhash); cudat.datasize=pwhash.length()+1; cudat.data=new char[cudat.datasize]; memcpy(cudat.data,pwhash.c_str(),pwhash.length()); cudat.data[pwhash.length()]='\0'; }else{ cudat.datasize=curformdat->Value.size()+1; cudat.data=new char[cudat.datasize]; memcpy(cudat.data,curformdat->Value.data(), curformdat->Value.size()); cudat.data[curformdat->Value.size()]='\0'; } } udat.Data->append(cudat); delete[] cudat.data; } User user; user.create(_Backend,&udat); std::cout << "user created!" << std::endl; } libhtmlpp::HtmlPage page; libhtmlpp::HtmlString form,out; form << "
" << "" << "" << "" << "" << "" << "" << "" << "" << "" << "" << "" << "" << "" << "" << "
" << "insertChild(form.parse()); libhtmlpp::print(&root,out,true); rep.setContentType("text/html"); rep.setContentLength(out.size()); rep.send(curreq,out.c_str(),out.size()); } void removeuser(libhttppp::HttpRequest *curreq, const int tid, ULONG_PTR args){ char uid[255]; sscanf(curreq->getRequestURL(),"/admin/removeuser/%s",uid); User user; uuid_t uuid; uuid_parse(uid,uuid); user.remove(_Backend,uuid); libhttppp::HttpResponse rep; rep.setState(HTTP307); rep.setVersion(HTTPVERSION(1.1)); rep.setHeaderData("Location")->push_back("/admin/listusers"); rep.setContentType("text/html"); rep.send(curreq,nullptr,0); } void getAvatar(libhttppp::HttpRequest *curreq, const int tid, ULONG_PTR args){ libhttppp::HttpResponse rep; char cuid[255],ext[16]; sscanf(curreq->getRequestURL(),"/admin/getavatar/%[^.].%s",cuid,ext); uuid_t uid; uuid_parse(cuid,uid); size_t rd=sizeof(authdb::AuthHeader),end=_Backend.end(); while(rdgetRequestURL(),"/admin/edituser/%s",cuid); uuid_t uid; uuid_parse(cuid,uid); AuthData editrec(uid); bool data = false; libhttppp::HttpForm curform; curform.parse(curreq); for (libhttppp::HttpForm::MultipartForm::Data* curformdat = curform.MultipartFormData.getFormData(); curformdat; curformdat = curformdat->nextData()) { for(libhttppp::HttpForm::MultipartForm::Data::ContentDisposition *curdispo=curformdat->getDisposition(); curdispo; curdispo=curdispo->nextContentDisposition() ){ AuthData curec(uid); if(curformdat->Value.empty() || !curdispo->getValue()) continue; data = true; curec.Data->type=UserData; curec.Data->storage=TextStorage; for(libhttppp::HttpForm::MultipartForm::Data::ContentDisposition *curdispo=curformdat->getDisposition(); curdispo; curdispo=curdispo->nextContentDisposition() ){ std::string key; if(curdispo->getKey()) key=curdispo->getKey(); else continue; std::transform(key.begin(), key.end(), key.begin(), [](unsigned char c){ return std::tolower(c); }); if(key=="name"){ strcpy(curec.Data->fieldname,curdispo->getValue()); } if(key=="filename"){ curec.Data->storage=BinaryStorage; } } if(curec.Data->storage==TextStorage && strcmp(curec.Data->fieldname,"pwhash")==0){ Hash hash; std::string pwhash,inpw; std::copy(curformdat->Value.begin(),curformdat->Value.end(), std::back_inserter(inpw)); hash.hash(inpw,pwhash); curec.Data->datasize=pwhash.length()+1; curec.Data->data=new char[curec.Data->datasize]; memcpy(curec.Data->data,pwhash.c_str(),pwhash.length()); curec.Data->data[pwhash.length()]='\0'; }else{ curec.Data->datasize=curformdat->Value.size()+1; curec.Data->data=new char[curec.Data->datasize]; memcpy(curec.Data->data,curformdat->Value.data(), curformdat->Value.size()); curec.Data->data[curformdat->Value.size()]='\0'; } editrec.append(curec); delete[] curec.Data->data; } } if(data){ editRecord(_Backend,editrec,UserData); libhttppp::HttpResponse rep; rep.setState(HTTP307); rep.setVersion(HTTPVERSION(1.1)); rep.setHeaderData("Location")->push_back("/admin/listusers"); rep.setContentType("text/html"); rep.send(curreq,nullptr,0); return; } libhtmlpp::HtmlPage page; libhtmlpp::HtmlString form,out; form << "" << ""; while(rd" << ""; }catch(AuthBackendError &e){ std::cerr << e.what() << std::endl; } delete[] cur.data; } } form << "" << "" << "" << "
" << "insertChild(form.parse()); libhtmlpp::print(&root,out,true); libhttppp::HttpResponse rep; rep.setContentType("text/html"); rep.setContentLength(out.size()); rep.send(curreq,out.c_str(),out.size()); } void listDomains(libhttppp::HttpRequest *curreq, const int tid, ULONG_PTR args){ size_t rd=sizeof(authdb::AuthHeader),end=_Backend.end(); libhtmlpp::HtmlString list,out; libhtmlpp::HtmlElement root=_IndexElement; libhtmlpp::HtmlElement *content=root.getElementbyID("content"); Domain domain; list <<"

DomainList:

"; while(rd"; }catch(AuthBackendError &e){ std::cerr << e.what() << std::endl; } } } list << "
" << ddat.getDomainName() << "
  • " << "Domainid: " << crdid << "
  • " << "
  • Storagetype: " << ddat.getStorageType() << "
  • " << "
  • Storageoptions: " << ddat.getStorageOptions() << "
  • " << "
"; content->insertChild(list.parse()); libhtmlpp::print(&root,out,true); libhttppp::HttpResponse rep; rep.setContentType("text/html"); rep.setContentLength(out.size()); rep.send(curreq,out.c_str(),out.size()); } void createDomain(libhttppp::HttpRequest *curreq, const int tid, ULONG_PTR args){ libhttppp::HttpForm curform; curform.parse(curreq); if (curform.getBoundary()) { uuid_t uid; uuid_generate(uid); class DomainData ddat(uid); for (libhttppp::HttpForm::MultipartForm::Data* curformdat = curform.MultipartFormData.getFormData(); curformdat; curformdat = curformdat->nextData()) { if(curformdat->Value.empty()) continue; std::string name; int stortype=TextStorage; for(libhttppp::HttpForm::MultipartForm::Data::ContentDisposition *curdispo=curformdat->getDisposition(); curdispo; curdispo=curdispo->nextContentDisposition() ){ std::string tmp=curdispo->getKey(); std::transform(tmp.begin(), tmp.end(), tmp.begin(), [](unsigned char c){ return std::tolower(c); }); if(tmp=="name") name=curdispo->getValue(); if(tmp=="filename"){ stortype=BinaryStorage; continue; } bool numeric=true; for(auto t=curformdat->Value.begin(); t!=curformdat->Value.end(); ++t){ if(!std::isdigit(*t)){ numeric=false; break; } } if(numeric){ stortype=StorageType::IntStorage; } } if(name.empty() || stortype == EmptyStorage) continue; struct AuthData::Record dodat; memset(&dodat,0,sizeof(AuthData::Record)); dodat.start=0xFE; uuid_copy(dodat.uuid,uid); dodat.type=UserData; dodat.storage=stortype; strcpy(dodat.fieldname,name.c_str()); if(dodat.storage==TextStorage){ dodat.datasize=curformdat->Value.size()+1; dodat.data=new char[dodat.datasize]; memcpy(dodat.data,curformdat->Value.data(), curformdat->Value.size()); dodat.data[curformdat->Value.size()]='\0'; ddat.Data->append(dodat); delete[] dodat.data; }else if(dodat.storage==IntStorage){ dodat.datasize=sizeof(int); dodat.data=(char*)new int; int val=0; std::copy(curformdat->Value.begin(),curformdat->Value.end(),&val); memcpy(dodat.data,&val, dodat.datasize); ddat.Data->append(dodat); delete (int*)dodat.data; }else { dodat.datasize=curformdat->Value.size(); dodat.data=new char[dodat.datasize]; memcpy(dodat.data,curformdat->Value.data(), dodat.datasize); ddat.Data->append(dodat); delete[] dodat.data; } } Domain domain; domain.create(_Backend,&ddat); std::cout << "domain created!" << std::endl; } libhtmlpp::HtmlString form,out; libhtmlpp::HtmlElement root=_IndexElement; libhtmlpp::HtmlElement *content=root.getElementbyID("content"); form << "" << "" << "" << "" << "" << "" << "" << "" << "" << "
"; content->insertChild(form.parse()); libhtmlpp::print(&root,out,true); libhttppp::HttpResponse rep; rep.setContentType("text/html"); rep.setContentLength(out.size()); rep.send(curreq,out.c_str(),out.size()); } void removeDomain(libhttppp::HttpRequest *curreq, const int tid, ULONG_PTR args){ char uid[255]; sscanf(curreq->getRequestURL(),"/admin/removedomain/%s",uid); Domain domain; uuid_t uuid; uuid_parse(uid,uuid); domain.remove(_Backend,uuid); libhttppp::HttpResponse rep; rep.setState(HTTP307); rep.setVersion(HTTPVERSION(1.1)); rep.setHeaderData("Location")->push_back("/admin/listdomains"); rep.setContentType("text/html"); rep.send(curreq,nullptr,0); } void AdminController(libhttppp::HttpRequest *curreq, const int tid, ULONG_PTR args){ _Backend.lock(); try { if(strncmp(curreq->getRequestURL(),"/admin/listusers",16)==0){ listUsers(curreq,tid,args); }else if(strncmp(curreq->getRequestURL(),"/admin/createuser",17)==0){ createUser(curreq,tid,args); }else if(strncmp(curreq->getRequestURL(),"/admin/removeuser",17)==0){ removeuser(curreq,tid,args); }else if(strncmp(curreq->getRequestURL(),"/admin/edituser",15)==0){ editUser(curreq,tid,args); }else if(strncmp(curreq->getRequestURL(),"/admin/getavatar/",17)==0){ getAvatar(curreq,tid,args); }else if(strncmp(curreq->getRequestURL(),"/admin/listdomains/",18)==0){ listDomains(curreq,tid,args); }else if(strncmp(curreq->getRequestURL(),"/admin/createdomain/",19)==0){ createDomain(curreq,tid,args); }else if(strncmp(curreq->getRequestURL(),"/admin/removedomain/",19)==0){ removeDomain(curreq,tid,args); }else if(strncmp(curreq->getRequestURL(),"/admin/authdb.js",16)==0){ libhttppp::HttpResponse rep; rep.setContentType("text/javascript"); rep.send(curreq,_AuthJs.data(),_AuthJs.size()); }else{ libhtmlpp::HtmlElement root=_IndexElement; libhtmlpp::HtmlString out; libhtmlpp::print(&root,out,true); libhttppp::HttpResponse rep; rep.setContentType("text/html"); rep.setContentLength(out.size()); rep.send(curreq,out.c_str(),out.size()); } }catch(AuthBackendError &e){ libhttppp::HttpResponse rep; rep.setState(HTTP500); rep.setContentType("text/html"); rep.send(curreq,e.what(),strlen(e.what())); } _Backend.unlock(); } void RequestEvent(libhttppp::HttpRequest *curreq, const int tid, ULONG_PTR args){ if(strncmp(curreq->getRequestURL(),"/admin",6)==0){ AdminController(curreq,tid,args); }else if(strncmp(curreq->getRequestURL(),"/api",4)==0){ }else{ libhttppp::HttpResponse rep; rep.setState(HTTP307); rep.setVersion(HTTPVERSION(1.1)); rep.setHeaderData("Location")->push_back("/admin"); rep.setContentType("text/html"); rep.send(curreq,nullptr,0); } }; private: authdb::AuthBackend _Backend; libhtmlpp::HtmlElement _IndexElement; libhtmlpp::HtmlPage _Indexpage; std::vector _AuthJs; }; int searchValue(authdb::AuthBackend &backend,const char*fieldname,const char *value){ authdb::AuthData::Record *user=new AuthData::Record; int rd=sizeof(authdb::AuthHeader),brd=rd; while(rd>backend.end()){ backend.setPos(rd); backend.read((unsigned char*)user,sizeof(AuthData::Record)); rd=backend.getPos(); if(strcmp(user->fieldname,fieldname) == 0){ user->data = new char[user->datasize]; backend.read((unsigned char*)user->data,user->datasize); if(strcmp(user->data,value) == 0){ delete[] user->data; goto VALUEFOUND; } delete[] user->data; } brd=rd; } VALUEFOUND: delete user; return brd; } AuthData::AuthData(const uuid_t id){ Data = new AuthData::Record; memset((void*)Data,0,sizeof(AuthData)); Data->start=0xFE; Data->storage=EmptyStorage; uuid_copy(Data->uuid,id); _next=nullptr; } AuthData::AuthData(const AuthData &src){ Data = new AuthData::Record; AuthData *dest=this; for(const AuthData *cursrc=&src; cursrc; cursrc=cursrc->next()){ memcpy(dest->Data,cursrc->Data,sizeof(AuthData::Record)); Data->data=new char[cursrc->Data->datasize]; memcpy(dest->Data->data,cursrc->Data->data,cursrc->Data->datasize); if(cursrc->next()){ dest->_next=new AuthData(this->Data->uuid); dest=dest->_next; } } } AuthData::~AuthData(){ delete Data; } AuthData *AuthData::next() const{ return _next; }; AuthData *AuthData::append(const AuthData &src){ AuthData *curec=this; while(curec->Data->datasize!=0){ if(!curec->_next){ curec->_next=new AuthData(Data->uuid); curec=curec->_next; break; } curec=curec->_next; } for(const AuthData *cuxrcrec=&src; cuxrcrec; cuxrcrec=cuxrcrec->next()){ memcpy(curec->Data,cuxrcrec->Data,sizeof(AuthData::Record)); curec->Data->data=new char[curec->Data->datasize]; memcpy(curec->Data->data,cuxrcrec->Data->data,curec->Data->datasize); curec->_next=nullptr; if(cuxrcrec->next()){ curec->_next=new AuthData(src.Data->uuid); curec=curec->_next; } } return curec; } AuthData *AuthData::append(const AuthData::Record &src){ AuthData *curec=this; while(curec->Data->datasize!=0){ if(!curec->_next){ curec->_next=new AuthData(Data->uuid); curec=curec->_next; break; } curec=curec->_next; } memcpy(curec->Data,&src,sizeof(AuthData::Record)); curec->Data->data=new char[curec->Data->datasize]; memcpy(curec->Data->data,src.data,curec->Data->datasize); return curec; } bool getRecord(authdb::AuthBackend &backend,AuthData &rec,int type){ bool found=false; int rd=sizeof(authdb::AuthHeader),end=backend.end(); AuthData *curec=&rec; while(rduuid) == 0 && cur.type == type){ cur.data=new char[cur.datasize]; backend.read((unsigned char*)cur.data,cur.datasize); rec.append(cur); delete[] cur.data; found=true; } } return found; } bool editRecord(AuthBackend &backend,AuthData &rec,int type){ AuthData old(rec.Data->uuid); std::vector changemap; //use fieldname; std::vector newmap; getRecord(backend,old,type); for(const AuthData *curdat=&rec; curdat; curdat=curdat->_next){ bool newentry=true; for(const AuthData *olddat=&old; olddat; olddat=olddat->_next){ if(uuid_compare(olddat->Data->uuid,curdat->Data->uuid)==0 && strcmp(olddat->Data->fieldname,curdat->Data->fieldname)==0 ){ size_t end = olddat->Data->datasize; if(end!=curdat->Data->datasize){ changemap.push_back(curdat->Data); }else{ for(size_t i=0; iData->data[i]!=curdat->Data->data[i]){ changemap.push_back(curdat->Data); break; } } } newentry=false; } } if(newentry){ newmap.push_back(curdat->Data); } } for(auto newel : newmap){ backend.setPos(backend.end()); backend.write((unsigned char*)newel,sizeof(AuthData::Record));; backend.write((unsigned char*)newel->data,newel->datasize); } const unsigned char zero = 0; for(auto chel : changemap){ size_t rd=sizeof(authdb::AuthHeader),end=backend.end(); while(rduuid,rec.Data->uuid)==0 && strcmp(rdrec->fieldname,chel->fieldname)==0){ backend.setPos(backend.end()); backend.write((const unsigned char*)chel,sizeof(AuthData::Record)); backend.write((const unsigned char*)chel->data,chel->datasize); size_t dsize=rdrec->datasize; memset(rdrec,0,sizeof(AuthData::Record)); rdrec->start = 0xFE; rdrec->datasize=dsize; backend.setPos(rd); backend.write((const unsigned char*)rdrec,sizeof(AuthData::Record)); for(size_t i =0; idatasize; ++i){ backend.write(&zero,1); } rd=backend.getPos(); }else{ rd=backend.getPos()+rdrec->datasize; } delete rdrec; } } if(!changemap.empty() || !newmap.empty()) return true; return false; } void delRecord(AuthBackend &backend,const uuid_t uid,int type){ size_t end=backend.end(),rd=sizeof(AuthHeader); if(!uid) throw AuthBackendError("user info uid required!"); while(rddatasize; if(uuid_compare(uid,cur->uuid) == 0 && type == cur->type){ size_t dsize=cur->datasize; memset(cur,0,sizeof(AuthData::Record)); cur->start = 0xFE; cur->datasize=dsize; backend.setPos(wr); backend.write((unsigned char*)cur,sizeof(AuthData::Record)); const unsigned char zero=0; for(size_t i=0; igetValue()); authdb::AuthBackend backend(authdb::AuthBackendType::File, config.getValue(config.getKey("/AUTHDB/ADMINDB/PATH"),0), "admin.local" ); try { if(backend.end()<=sizeof(authdb::AuthHeader)){ authdb::initalized=false; } libhttppp::HttpD httpd( config.getValue(config.getKey("/AUTHDB/BIND"),0), config.getIntValue(config.getKey("/AUTHDB/PORT"),0), config.getIntValue(config.getKey("/AUTHDB/MAXCONN"),0), nullptr, nullptr ); authdb::AuthDB authdb(backend,httpd.getServerSocket()); authdb.runEventloop(); }catch(authdb::AuthBackendError &e){ std::cerr << e.what() << std::endl; } }