// =============================== // PC-BSD REST/JSON API Server // Available under the 3-clause BSD License // Written by: Ken Moore July 2015 // ================================= #include "AuthorizationManager.h" #include "globals.h" // Stuff for PAM to work #include #include #include #include #include #include #include #include //Stuff for OpenSSL to work #include #include #include #include #include #include //Internal defines // -- token management #define TIMEOUTSECS 900 // (15 minutes) time before a token becomes invalid #define AUTHCHARS QString("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") #define TOKENLENGTH 40 AuthorizationManager::AuthorizationManager() : QObject(){ HASH.clear(); IPFAIL.clear(); //initialize the random number generator (need to generate auth tokens) qsrand(QDateTime::currentMSecsSinceEpoch()); } AuthorizationManager::~AuthorizationManager(){ } // == Token Interaction functions == void AuthorizationManager::clearAuth(QString token){ if(token.isEmpty() || token.length() < TOKENLENGTH){ return; } //not a valid token //clear an authorization token QString id = hashID(token); //qDebug() << "Clear Auth:" << id; if(!id.isEmpty()){ HASH.remove(id); } } bool AuthorizationManager::checkAuth(QString token){ //see if the given token is valid bool ok = false; QString id = hashID(token); if(!id.isEmpty()){ //Also verify that the token has not timed out ok = (HASH[id] > QDateTime::currentDateTime()); if(ok){ HASH.insert(id, QDateTime::currentDateTime().addSecs(TIMEOUTSECS)); } //valid - bump the timestamp } return ok; } bool AuthorizationManager::hasFullAccess(QString token){ bool ok = false; QString id = hashID(token); if(!id.isEmpty()){ //Also verify that the token has not timed out if( HASH[id] > QDateTime::currentDateTime() ){ ok = id.section("::::",1,1)=="operator"; } } return ok; } //SSL Certificate register/revoke/list bool AuthorizationManager::RegisterCertificate(QString token, QString pubkey, QString nickname, QString email){ if(!checkAuth(token)){ return false; } QString user = hashID(token).section("::::",2,2); //get the user name from the currently-valid token //NOTE: The public key should be a base64 encoded string CONFIG->setValue("RegisteredCerts/"+user+"/"+pubkey, "Nickname: "+nickname+"\nEmail: "+email+"\nDate Registered: "+QDateTime::currentDateTime().toString(Qt::ISODate) ); return true; } bool AuthorizationManager::RevokeCertificate(QString token, QString key, QString user){ //user will be the current user if not empty - cannot touch other user's certs without full perms on current session QString cuser = hashID(token).section("::::",2,2); if(user.isEmpty()){ user = cuser; } //only probe current user if(user !=cuser){ //Check permissions for this cross-user action if(!hasFullAccess(token)){ return false; } } //Check that the given cert exists first if( !CONFIG->contains("RegisteredCerts/"+user+"/"+key) ){ return false; } CONFIG->remove("RegisteredCerts/"+user+"/"+key); return true; } void AuthorizationManager::ListCertificates(QString token, QJsonObject *out){ QStringList keys; //Format: "RegisteredCerts//" if( hasFullAccess(token) ){ //Read all user's certs keys = CONFIG->allKeys().filter("RegisteredCerts/"); //qDebug() << "Found SSL Keys to List:" << keys; }else{ //Only list certs for current user QString cuser = hashID(token).section("::::",2,2); keys = CONFIG->allKeys().filter("RegisteredCerts/"+cuser+"/"); //qDebug() << "Found SSL Keys to List:" << keys; } keys.sort(); //Now put the known keys into the output structure arranged by username/key QJsonObject user; QString username; for(int i=0; iinsert(username, user); user = QJsonObject(); } //save the current info to the output username = keys[i].section("/",1,1); //save the new username for later } QString info = CONFIG->value(keys[i]).toString(); QString key = keys[i].section("/",2,-1);//just in case the key has additional "/" in it user.insert(key,info); } if(!user.isEmpty() && !username.isEmpty()){ out->insert(username, user); } } //Generic functions int AuthorizationManager::checkAuthTimeoutSecs(QString token){ //Return the number of seconds that a token is valid for if(!HASH.contains(token)){ return 0; } //invalid token return QDateTime::currentDateTime().secsTo( HASH[token] ); } // == Token Generation functions QString AuthorizationManager::LoginUP(QHostAddress host, QString user, QString pass){ //Login w/ username & password bool localhost = ( (host== QHostAddress::LocalHost) || (host== QHostAddress::LocalHostIPv6) || (host.toString()=="::ffff:127.0.0.1") ); bool ok = false; //First check that the user is valid on the system and part of the operator group bool isOperator = false; if(user!="root" && user!="toor"){ QStringList groups = getUserGroups(user); if(groups.contains("wheel")){ isOperator = true; } //full-access user else if(!groups.contains("operator")){ return ""; //user not allowed access if not in either of the wheel/operator groups } }else{ isOperator = true; } //qDebug() << "Check username/password" << user << pass << localhost; //Need to run the full username/password through PAM if(!localhost || user=="root" || user=="toor"){ ok = pam_checkPW(user,pass); }else{ ok = true; //allow local access for users without password } qDebug() << "User Login Attempt:" << user << " Success:" << ok << " IP:" << host.toString(); LogManager::log(LogManager::HOST, QString("User Login Attempt: ")+user+" Success: "+(ok?"true":"false")+" IP: "+host.toString() ); if(!ok){ //invalid login //Bump the fail count for this host bool overlimit = BumpFailCount(host.toString()); if(overlimit){ emit BlockHost(host); } return (overlimit ? "REFUSED" : ""); }else{ //valid login - generate a new token for it ClearHostFail(host.toString()); return generateNewToken(isOperator, user); } } QString AuthorizationManager::LoginService(QHostAddress host, QString service){ bool localhost = ( (host== QHostAddress::LocalHost) || (host== QHostAddress::LocalHostIPv6) || (host.toString()=="::ffff:127.0.0.1") ); //Login a particular automated service qDebug() << "Service Login Attempt:" << service << " Success:" << localhost; if(!localhost){ return ""; } //invalid - services must be local for access //Check that the service is valid on the system bool isok = false; if(service!="root" && service!="toor" && localhost){ QStringList groups = getUserGroups(service); isok = (groups.contains(service) && !groups.contains("wheel") && !groups.contains("operator")); } //Now generate a new token and send it back if(!isok){ //invalid login if(!localhost){ //Bump the fail count for this host bool overlimit = BumpFailCount(host.toString()); if(overlimit){ emit BlockHost(host); } return (overlimit ? "REFUSED" : ""); }else{ return ""; } }else{ return generateNewToken(false, service); }//services are never given operator privileges } //Stage 1 SSL Login Check: Generation of random string for this user QString AuthorizationManager::GenerateEncCheckString(){ QString key; for(int i=0; i HASH[pubkeys[i]]){ //Note: normally only 1 request per user at a time, but it is possible for a couple different clients to try // and authenticate as the same user (but different keys) at nearly the same time - so keep a short valid-string time frame (<30 seconds) // to mitigate this possibility (need to prevent the second user-auth request from invalidating the first before the first auth handshake is finished) HASH.remove(pubkeys[i]); //initstring expired - go ahead and remove it to reduce calc time later } } //Now re-use the "pubkeys" variable for the public SSL keys QString user; pubkeys = CONFIG->allKeys().filter("RegisteredCerts/"); //Format: "RegisteredCerts//" //qDebug() << " - Check pubkeys";// << pubkeys; for(int i=0; i::::" QStringList keys = QStringList(IPFAIL.keys()).filter(host+"::::"); int fails = 0; if(!keys.isEmpty()){ //Take the existing key/value and put a new one in (this limits the filter to 1 value maximum) QDateTime last = IPFAIL.take(keys[0]); if(last.addSecs(BlackList_AuthFailResetMinutes*60) > QDateTime::currentDateTime() ){ fails = keys[0].section("::::",1,1).toInt(); } } fails++; IPFAIL.insert(host+"::::"+QString::number(fails), QDateTime::currentDateTime() ); return (fails>=BlackList_AuthFailsToBlock); } void AuthorizationManager::ClearHostFail(QString host){ QStringList keys = QStringList(IPFAIL.keys()).filter(host+"::::"); for(int i=0; i