/******************************************************************************* * 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 <organization> 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 <COPYRIGHT HOLDER> 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 <algorithm> #include <iostream> #include <cstring> #include <sstream> #include <map> #include <uuid/uuid.h> #include <cmdplus/cmdplus.h> #include <confplus/conf.h> #include <httppp/httpd.h> #include <htmlpp/html.h> #include "backend.h" #include "authdb.h" #include "user.h" #include "types.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"); }; void listUsers(libhttppp::HttpRequest *curreq, const int tid, ULONG_PTR args){ _Backend.lock(); size_t rd=sizeof(authdb::AuthHeader),end=_Backend.end(); libhtmlpp::HtmlString userlist,out; libhtmlpp::HtmlElement root=_IndexElement; libhtmlpp::HtmlElement *content=root.getElementbyID("content"); User user; class UserData udat; userlist <<"<div><p>UserList:</p><table class=\"userlist\">"; while(rd<end){ authdb::AuthData *cur=new AuthData; _Backend.ReadAuthData(cur,rd); rd=_Backend.getPos()+cur->datasize; uuid_t uid; uuid_parse(cur->uuid,uid); if(strcmp(cur->fieldname,"username")==0){ try { size_t upos=sizeof(authdb::AuthHeader); user.info(_Backend,uid,udat,upos); userlist << "<tr><td class=\"list_username\">" << udat.getUsername() << "</td></tr>" << "<tr><td><img class=\"list_picture\" style=\"height:100px;\" src=\"/admin/getavatar/" << cur->uuid << ".jpg\" alt=\"avatar\">" << "</td><td><ul><li>" << "uid: " << cur->uuid << "</li>" << "<li><span>Firstname:</span><span>" << udat.getFirstname() << "</span></li>" << "<li><span>Lastname:</span><span>" << udat.getLastname() << "</span></li>" << "<li><span>Email:</span><span>" << udat.getMail() << "</span></li>" << "</ul></td><td><ul class=\"usertoolbar\" >" << "<li><a class=\"button\" href=\"/admin/edituser/" << cur->uuid << "\">EditUser</a></li>" << "<li><a class=\"button\" href=\"/admin/removeuser/" << cur->uuid << "\">RemoveUser</a></li>" << "</ul></td></tr>"; }catch(AuthBackendError &e){ std::cerr << e.what() << std::endl; } } delete cur; } _Backend.unlock(); userlist << "</table></div><div><ul class=\"usertoolbar\" ><li><a class=\"button\" href=\"/admin/createuser\">CreateUser</a></li></ul></div>"; 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()) { for(libhttppp::HttpForm::MultipartForm::Data::ContentDisposition *curdispo=curformdat->getDisposition(); curdispo; curdispo=curdispo->nextContentDisposition() ){ if(curformdat->Value.empty() || !curdispo->getValue()) continue; std::vector<char> tmp; if(strcmp(curdispo->getValue(),"avatar")!=0){ std::copy(curformdat->Value.begin(),curformdat->Value.end(),std::back_inserter(tmp)); tmp.push_back('\0'); } if(strcmp(curdispo->getValue(),"username")==0){ udat.setUserName(tmp.data()); }else if(strcmp(curdispo->getValue(),"firstname")==0){ udat.setFirstName(tmp.data()); }else if(strcmp(curdispo->getValue(),"lastname")==0){ udat.setLastName(tmp.data()); }else if(strcmp(curdispo->getValue(),"mail")==0){ udat.setMail(tmp.data()); }else if(strcmp(curdispo->getValue(),"avatar")==0 && !curformdat->Value.empty()){ udat.setAvatar(curformdat->Value); } } } _Backend.lock(); User user; user.create(_Backend,&udat); _Backend.unlock(); std::cout << "user created!" << std::endl; } libhtmlpp::HtmlPage page; libhtmlpp::HtmlString form,out; form << "<form method=\"post\" enctype=\"multipart/form-data\">" << "<table>" << "<tr><td><label for=\"username\">username:</label></td>" << "<td><input type=\"text\" name=\"username\"></td></tr>" << "<tr><td><label for=\"firstname\">firstname:</label></td>" << "<td><input type=\"text\" name=\"firstname\"></td></tr>" << "<tr><td><label for=\"lastname\">lastname:</label></td>" << "<td><input type=\"text\" name=\"lastname\"></td></tr>" << "<tr><td><label for=\"mail\">mail:</label></td>" << "<td><input type=\"text\" name=\"mail\"></td></tr>" << "<tr><td><label for=\"avatar\">ProfilBild:</label></td>" << "<td><input type=\"file\" name=\"avatar\"></td></tr>" << "<tr><td></td><td><input type=\"submit\"></td></tr>" << "</table>" << "</form"; libhtmlpp::HtmlElement root=_IndexElement; libhtmlpp::HtmlElement *content=root.getElementbyID("content"); content->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); _Backend.lock(); user.remove(_Backend,uuid); _Backend.unlock(); 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 uid[255],ext[16]; sscanf(curreq->getRequestURL(),"/admin/getavatar/%[^.].%s",uid,ext); _Backend.lock(); authdb::AuthData *cur; size_t rd=sizeof(authdb::AuthHeader),end=_Backend.end(); while(rd<end){ cur=new AuthData; cur->data=nullptr; _Backend.ReadAuthData(cur,rd); rd=_Backend.getPos()+cur->datasize; if(cur && strcmp(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; } delete cur; } if(end!=0){ rep.setState(HTTP404); rep.send(curreq,nullptr,0); } _Backend.unlock(); }; void editUser(libhttppp::HttpRequest *curreq, const int tid, ULONG_PTR args){ _Backend.lock(); size_t rd=sizeof(authdb::AuthHeader),end=_Backend.end(); char cuid[255]; sscanf(curreq->getRequestURL(),"/admin/edituser/%s",cuid); AuthDataRecord editrec,*curec=&editrec; 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() ){ if(curformdat->Value.empty() || !curdispo->getValue()) continue; data = true; strcpy(curec->Data->uuid,cuid); 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(); std::transform(key.begin(), key.end(), key.begin(), [](unsigned char c){ return std::tolower(c); }); if(key=="name"){ strcpy(curec->Data->fieldname,curdispo->getValue()); }else if(key=="content-type" && strncmp(curdispo->getValue(),"text/",5) != 0){ curec->Data->storage=BinaryStorage; } } curec->Data->datasize=curformdat->Value.size(); curec->Data->data=new char[curec->Data->datasize]; memcpy(curec->Data->data,curformdat->Value.data(),curformdat->Value.size()); } if(curformdat->nextData()){ curec->_next=new AuthDataRecord; curec=curec->_next; } } uuid_t uid; uuid_parse(cuid,uid); if(data){ editRecord(_Backend,&editrec,uid,UserData); _Backend.unlock(); 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 << "<form method=\"post\" enctype=\"multipart/form-data\">" << "<table>"; while(rd<end){ authdb::AuthData *cur=new AuthData; _Backend.ReadAuthData(cur,rd); cur->data = new char[cur->datasize]; _Backend.read((unsigned char*)cur->data,cur->datasize); rd=_Backend.getPos(); if(strcmp(cur->uuid,cuid)==0 && cur->type==UserData && cur->storage==TextStorage){ try { form << "<tr><td><label for=\"username\">" << cur->fieldname << "</label></td>" << "<td><input type=\"text\" name=\"" << cur->fieldname << "\" value=\"" << cur->data <<"\"></td></tr>"; }catch(AuthBackendError &e){ std::cerr << e.what() << std::endl; } } delete[] cur->data; delete cur; } _Backend.unlock(); form << "<tr><td><label for=\"avatar\">ProfilBild:</label></td>" << "<td><input type=\"file\" name=\"avatar\"></td></tr>" << "<tr><td></td><td><input type=\"submit\"></td></tr>" << "</table>" << "</form"; libhtmlpp::HtmlElement root=_IndexElement; libhtmlpp::HtmlElement *content=root.getElementbyID("content"); 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 AdminController(libhttppp::HttpRequest *curreq, const int tid, ULONG_PTR args){ 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{ 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()); } } 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; }; int searchValue(authdb::AuthBackend &backend,const char*fieldname,const char *value){ authdb::AuthData *user=new AuthData; int rd=sizeof(authdb::AuthHeader),brd=rd; while(rd>backend.end()){ backend.ReadAuthData(user,rd); 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; } AuthDataRecord::AuthDataRecord(){ Data = new AuthData; memset((void*)Data,0,sizeof(AuthData)); Data->start=0xFE; _next=nullptr; } AuthDataRecord::AuthDataRecord(const AuthDataRecord &src){ Data = new AuthData; memcpy(&Data,&src.Data,sizeof(src.Data)); memcpy(Data->data,src.Data->data,src.Data->datasize); _next=nullptr; } AuthDataRecord::~AuthDataRecord(){ delete Data; } AuthDataRecord *AuthDataRecord::next(){ return _next; }; bool getRecord(authdb::AuthBackend &backend,AuthDataRecord *dest,const uuid_t id,int type){ bool found=false; AuthData *cur=new AuthData; int rd=sizeof(authdb::AuthHeader),end=backend.end(); char plainid[255]; uuid_unparse(id,plainid); AuthDataRecord *curec=dest; while(rd<end){ backend.ReadAuthData(cur,rd); rd=backend.getPos()+cur->datasize; if(strcmp(cur->uuid,plainid) == 0 && cur->type == type){ memcpy(curec->Data,cur,sizeof(AuthData)); curec->Data->data=new char[cur->datasize]; backend.read((unsigned char*)curec->Data->data,cur->datasize); if(rd<backend.end()){ curec->_next=new AuthDataRecord; curec=curec->_next; } found=true; } } delete cur; return found; } bool editRecord(AuthBackend &backend,const AuthDataRecord *rec,const uuid_t uid,int type){ AuthDataRecord old; std::vector<AuthData*> changemap; //use fieldname; std::vector<AuthData*> newmap; getRecord(backend,&old,uid,type); for(const AuthDataRecord *curdat=rec; curdat; curdat=curdat->_next){ bool newentry=true; for(const AuthDataRecord *olddat=&old; olddat; olddat=olddat->_next){ if(strcmp(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; i<end; ++i){ if(olddat->Data->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.WriteAuthData(newel,backend.end()); backend.setPos(backend.end()); backend.write((const unsigned char*)newel->data,newel->datasize); } char cuid[255]; const unsigned char zero = 0; uuid_unparse(uid,cuid); for(auto chel : changemap){ size_t rd=sizeof(authdb::AuthHeader),end=backend.end(); while(rd<end){ authdb::AuthData *rdrec=new AuthData; backend.ReadAuthData(rdrec,rd); if(strcmp(rdrec->uuid,cuid)==0 && strcmp(rdrec->fieldname,chel->fieldname)==0){ backend.WriteAuthData(chel,backend.end()); backend.write((const unsigned char*)chel->data,chel->datasize); size_t dsize=rdrec->datasize; memset(rdrec,0,sizeof(AuthData)); rdrec->start = 0xFE; rdrec->datasize=dsize; backend.WriteAuthData(rdrec,rd); for(size_t i =0; i<rdrec->datasize; ++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){ size_t end=backend.end(),rd=sizeof(AuthHeader); char cuid[255]; if(!uid) throw AuthBackendError("user info uid required!"); uuid_unparse(uid,cuid); while(rd<end){ authdb::AuthData *cur=new AuthData; size_t wr=backend.getPos(); backend.ReadAuthData(cur,rd); rd=backend.getPos()+cur->datasize; if(strlen(cur->uuid) >0 && strncmp(cuid,cur->uuid,strlen(cur->uuid)) == 0){ // std::cout << cur->uuid << std::endl; size_t dsize=cur->datasize; memset(cur,0,sizeof(AuthData)); cur->start = 0xFE; cur->datasize=dsize; backend.WriteAuthData(cur,wr); const unsigned char zero=0; for(size_t i=0; i<dsize; ++i){ backend.write(&zero,1); } } delete cur; } } }; int main(int argc, char *argv[]){ cmdplus::CmdController &cmd=cmdplus::CmdController::getInstance(); cmd.registerCmd("config",'c',true,nullptr,"Config Path"); cmd.parseCmd(argc,argv); if(!cmd.checkRequired()){ std::cerr << "Config Path required !" << std::endl; cmd.printHelp(); return -1; } confplus::Config config(cmd.getCmdbyKey("config")->getValue()); 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; } }