/******************************************************************************* * 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 #include "backend.h" #include "authdb.h" #include "user.h" #include "domain.h" #include "group.h" #include "types.h" #include "hash.h" #include "hash/sha512.h" #include "admin.h" namespace authdb { std::mutex g_wizzard; bool initalized=true; class AuthDB : public libhttppp::HttpEvent{ public: AuthDB(authdb::AuthBackend &backend,netplus::socket *ssock) : HttpEvent(ssock), _AdminBackend(backend), _AdminInterface(backend,ssock){ std::ifstream authfs(GETDATA(authdb.js), std::ios::ate); size_t asize = authfs.tellg(); _AuthJs.resize(asize); authfs.seekg(0); authfs.read(_AuthJs.data(),asize); std::ifstream cssfs(GETDATA(style.css), std::ios::ate); size_t csssize = cssfs.tellg(); _AuthCss.resize(csssize); cssfs.seekg(0); cssfs.read(_AuthCss.data(),csssize); }; void WizzardEvent(libhttppp::HttpRequest * curreq, const int tid, ULONG_PTR args){ std::lock_guard wlock(g_wizzard); libhttppp::HttpForm form; form.parse(curreq); std::string username,password,pwhash; for(libhttppp::HttpForm::UrlcodedForm::Data *curformdat=form.UrlFormData.getFormData(); curformdat; curformdat=curformdat->nextData()){ if(strcmp(curformdat->getKey(),"username")==0) username=curformdat->getValue(); else if(strcmp(curformdat->getKey(),"password")==0) password=curformdat->getValue(); } if(!password.empty() && !username.empty()){ sha512 hash; hash.hash(password,pwhash); authdb::User user; uuid_t adminid,admingid; uuid_generate(adminid); uuid_generate(admingid); class authdb::UserData udat(adminid); udat.setUserName(username.c_str()); authdb::sha512 sha; sha.hash(password,pwhash); udat.setPwHash(pwhash.c_str()); user.create(_AdminBackend,&udat); authdb::Group group; class authdb::GroupData gdat(admingid); gdat.setName("SuperAdmin"); authdb::GroupData::MemberUids muid; muid.count=1; muid.uid=&adminid; gdat.addMember(muid); group.create(_AdminBackend,&gdat); libhttppp::HTTPException exp; exp[999] << "wizzard finished"; throw exp; } libhtmlpp::HtmlElement index; libhtmlpp::HtmlPage page; page.loadFile(index,GETDATA(wizzard.html)); libhttppp::HttpResponse rep; libhtmlpp::HtmlString sendat; libhtmlpp::print((libhtmlpp::Element*)&index,sendat,false); rep.setState(HTTP200); rep.setVersion(HTTPVERSION(1.1)); rep.setContentType("text/html"); rep.send(curreq,sendat.c_str(),sendat.size()); } void RequestEvent(libhttppp::HttpRequest * curreq, const int tid, ULONG_PTR args){ if(_AdminBackend.end()<=sizeof(authdb::AuthHeader)){ try{ WizzardEvent(curreq,tid,args); return; }catch(libhttppp::HTTPException &e){ if(e.getErrorType()!=999) throw e; } } if(strncmp("/settings",curreq->getRequestURL(),9)==0){ _AdminBackend.lock(); try{ _AdminInterface.Request(curreq,tid,args); }catch(AuthBackendError &e){ libhttppp::HttpResponse rep; rep.setState(HTTP500); rep.setContentType("text/html"); rep.send(curreq,e.what(),strlen(e.what())); } _AdminBackend.unlock(); }else if(strncmp("/getavatar",curreq->getRequestURL(),9)==0){ _AdminBackend.lock(); try{ getAvatar(curreq,tid,args); }catch(AuthBackendError &e){ libhttppp::HttpResponse rep; rep.setState(HTTP500); rep.setContentType("text/html"); rep.send(curreq,e.what(),strlen(e.what())); } _AdminBackend.unlock(); }else if(strncmp("/authdb.js",curreq->getRequestURL(),9)==0){ libhttppp::HttpResponse rep; rep.setState(HTTP200); rep.setContentType("text/javascript"); rep.send(curreq,_AuthJs.data(),_AuthJs.size()); }else if(strncmp("/style.css",curreq->getRequestURL(),9)==0){ libhttppp::HttpResponse rep; rep.setState(HTTP200); rep.setContentType("text/css"); rep.send(curreq,_AuthCss.data(),_AuthCss.size()); }else{ libhttppp::HttpResponse rep; rep.setState(HTTP404); rep.setContentType("text/html"); rep.send(curreq,nullptr,0); } } void getAvatar(libhttppp::HttpRequest *curreq, const int tid, ULONG_PTR args){ libhttppp::HttpResponse rep; DomainBackend dbackend(_AdminBackend); AuthBackend *backend=nullptr; char domain[255],cuid[255],ext[16]; if(sscanf(curreq->getRequestURL(),"/getavatar/%[^/]/%[^.].%s",domain,cuid,ext)<0) return; uuid_t did; if(strcmp(domain,"admin") == 0 ){ backend=&_AdminBackend; }else if(uuid_parse(domain,did) == 0 ){ backend=dbackend.data(did); }else{ throw AuthBackendError("createUser: could not parse uuid!"); } uuid_t uid; uuid_parse(cuid,uid); size_t rd=sizeof(authdb::AuthHeader),end=backend->end(); while(rdsetPos(rd); backend->read((unsigned char*)&cur,sizeof(AuthData::Record)); rd=backend->getPos()+cur.datasize; if(uuid_compare(cur.uuid,uid)==0 && strcmp(cur.fieldname,"avatar")==0){ cur.data = new char[cur.datasize]; backend->read((unsigned char*)cur.data,cur.datasize); char ctype[255]; snprintf(ctype,255,"image/%s",ext); rep.setContentType(ctype); rep.send(curreq,cur.data,cur.datasize); end=0; delete[] cur.data; } } if(end!=0){ rep.setState(HTTP404); rep.send(curreq,nullptr,0); } }; private: AuthBackend &_AdminBackend; AdminInterface _AdminInterface; std::vector _AuthJs; std::vector _AuthCss; }; 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 struct AuthData::Record; memset(Data,0,sizeof(AuthData::Record)); uuid_copy(Data->uuid,id); Data->start=0xFE; Data->storage=EmptyStorage; Data->type=DataType::EmptyData; Data->fieldname[0]='\0'; Data->data=nullptr; Data->datasize=0; _next=nullptr; } AuthData::AuthData(const AuthData &src) : AuthData(src.Data->uuid){ AuthData *dest=this; for(const AuthData *cursrc=&src; cursrc; cursrc=cursrc->next()){ memcpy(dest->Data,cursrc->Data,sizeof(AuthData::Record)); dest->Data->datasize=cursrc->Data->datasize; dest->Data->data=new char[dest->Data->datasize]; memcpy(dest->Data->data,cursrc->Data->data,cursrc->Data->datasize); if(cursrc->next()){ dest->_next=new AuthData(cursrc->Data->uuid); dest=dest->_next; } } } AuthData::~AuthData(){ delete Data; } AuthData *AuthData::next() const{ return _next; }; bool authdb::AuthData::empty(){ if(!Data->data) return true; return false; }; AuthData *AuthData::append(const AuthData &src){ AuthData *curec=this,*next=this; if(!curec->_next && curec->Data->datasize==0){ for(const AuthData *cursrc=&src; cursrc; cursrc=cursrc->next()){ next=next->append(*cursrc->Data); } return curec; } while(curec){ if(!curec->_next){ curec->_next=new AuthData(src); curec=curec->_next; break; } curec=curec->_next; }; return curec; } AuthData *AuthData::append(const AuthData::Record &src){ AuthData *curec=this; if(!curec->_next && curec->Data->datasize==0){ 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; } while(curec){ if(!curec->_next){ curec->_next=new AuthData(Data->uuid); curec=curec->_next; curec->append(src); break; } curec=curec->_next; }; 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; if(rec.Data->datasize==0) continue; 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 { 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.threads=2; authdb.runEventloop(); }catch(authdb::AuthBackendError &e){ std::cerr << e.what() << std::endl; } }