-
jan.koester authoredjan.koester authored
authdb.cpp 19.60 KiB
/*******************************************************************************
* 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 <mutex>
#include <map>
#include <fstream>
#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 "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<std::mutex> 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(strncmp("/authdb.js",curreq->getRequestURL(),9)==0){
libhttppp::HttpResponse rep;
rep.setState(HTTP200);
rep.setContentType("text/javascript");
rep.send(curreq,_AuthJs.data(),_AuthJs.size());
return;
}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());
return;
}else if(_AdminBackend.end()<=sizeof(authdb::AuthHeader)){
try{
_AdminBackend.lock();
WizzardEvent(curreq,tid,args);
_AdminBackend.unlock();
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{
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(rd<end){
authdb::AuthData::Record cur;
backend->setPos(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<char> _AuthJs;
std::vector<char> _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(rd<end){
AuthData::Record cur;
memset(&cur,0,sizeof(AuthData::Record));
backend.setPos(rd);
backend.read((unsigned char*)&cur,sizeof(AuthData::Record));
rd=backend.getPos()+cur.datasize;
if(uuid_compare(cur.uuid,rec.Data->uuid) == 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<AuthData::Record*> changemap; //use fieldname;
std::vector<AuthData::Record*> 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; 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.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(rd<end){
authdb::AuthData::Record *rdrec=new AuthData::Record;
backend.setPos(rd);
backend.read((unsigned char*)rdrec,sizeof(AuthData::Record));
if(uuid_compare(rdrec->uuid,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; 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,int type){
size_t end=backend.end(),rd=sizeof(AuthHeader);
if(!uid)
throw AuthBackendError("user info uid required!");
while(rd<end){
authdb::AuthData::Record *cur=new AuthData::Record;
backend.setPos(rd);
size_t wr=backend.getPos();
backend.read((unsigned char*)cur,sizeof(AuthData::Record));
rd=backend.getPos()+cur->datasize;
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; i<dsize; ++i){
backend.write(&zero,1);
}
}
delete cur;
}
}
DomainBackend::DomainBackend(authdb::AuthBackend &adminbck) : _adminbck(adminbck) {
_domainbck=nullptr;
}
authdb::AuthBackend *DomainBackend::data(uuid_t did){
size_t rd=sizeof(authdb::AuthHeader),end=_adminbck.end();
while(rd<end){
authdb::AuthData::Record cur;
_adminbck.setPos(rd);
_adminbck.read((unsigned char*)&cur,sizeof(AuthData::Record));
rd=_adminbck.getPos()+cur.datasize;
if(uuid_compare(cur.uuid,did) == 0 && cur.type == DataType::DomainData &&
strcmp(cur.fieldname,"domainname")==0){
class DomainData ddat(cur.uuid);
size_t dpos=sizeof(authdb::AuthHeader);
Domain domain;
domain.info(_adminbck,ddat,dpos);
_domainbck = new AuthBackend(ddat.getStorageType(),ddat.getStorageOptions(),ddat.getDomainName());
}
}
return _domainbck;
}
DomainBackend::~DomainBackend(){
delete _domainbck;
}
};
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 {
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;
}
}