RMCP+ login support with privilege

Implementation of RMCP login support with appropriate
privilege level.

Unit Test:
1. Verified that user is able to login without any issues
2. Privilege of the user is minimum of requested, user & channel
3. Unable to set higher privilege using Set session commands

Change-Id: I5e9ef21dfc1f1b50aa815562a3a65d90c434877c
Signed-off-by: Richard Marian Thomaiyar <richard.marian.thomaiyar@linux.intel.com>
diff --git a/Makefile.am b/Makefile.am
index 5ed3eec..352a20c 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -65,6 +65,7 @@
 	$(PHOSPHOR_LOGGING_LIBS) \
 	$(PHOSPHOR_DBUS_INTERFACES_LIBS) \
 	$(LIBADD_DLOPEN) \
+	-luserlayer \
 	-export-dynamic
 
 netipmid_CXXFLAGS = \
diff --git a/command/rakp12.cpp b/command/rakp12.cpp
index b4842b2..5384ab3 100644
--- a/command/rakp12.cpp
+++ b/command/rakp12.cpp
@@ -11,6 +11,8 @@
 #include <cstring>
 #include <iomanip>
 #include <iostream>
+#include <user_channel/channel_layer.hpp>
+#include <user_channel/user_layer.hpp>
 
 namespace command
 {
@@ -61,15 +63,6 @@
 
     session->userName.assign(request->user_name, request->user_name_len);
 
-    // Validate the user name if the username is provided
-    if (request->user_name_len &&
-        (session->userName != cipher::rakp_auth::userName))
-    {
-        response->rmcpStatusCode =
-            static_cast<uint8_t>(RAKP_ReturnCode::UNAUTH_NAME);
-        return outPayload;
-    }
-
     // Update transaction time
     session->updateLastTransactionTime();
 
@@ -129,6 +122,80 @@
         return outPayload;
     }
 
+    session->reqMaxPrivLevel = request->req_max_privilege_level;
+    session->curPrivLevel = static_cast<session::Privilege>(
+        request->req_max_privilege_level & session::reqMaxPrivMask);
+    if (((request->req_max_privilege_level & userNameOnlyLookupMask) !=
+         userNameOnlyLookup) ||
+        (request->user_name_len == 0))
+    {
+        // Skip privilege based lookup for security purpose
+        response->rmcpStatusCode =
+            static_cast<uint8_t>(RAKP_ReturnCode::UNAUTH_NAME);
+        return outPayload;
+    }
+
+    // Perform user name based lookup
+    std::string userName(request->user_name, request->user_name_len);
+    std::string passwd;
+    uint8_t userId = ipmi::ipmiUserGetUserId(userName);
+    if (userId == ipmi::invalidUserId)
+    {
+        response->rmcpStatusCode =
+            static_cast<uint8_t>(RAKP_ReturnCode::UNAUTH_NAME);
+        return outPayload;
+    }
+    // check user is enabled before proceeding.
+    bool userEnabled = false;
+    ipmi::ipmiUserCheckEnabled(userId, userEnabled);
+    if (!userEnabled)
+    {
+        response->rmcpStatusCode =
+            static_cast<uint8_t>(RAKP_ReturnCode::INACTIVE_ROLE);
+        return outPayload;
+    }
+    // Get the user password for RAKP message authenticate
+    passwd = ipmi::ipmiUserGetPassword(userName);
+    if (passwd.empty())
+    {
+        response->rmcpStatusCode =
+            static_cast<uint8_t>(RAKP_ReturnCode::UNAUTH_NAME);
+        return outPayload;
+    }
+    ipmi::PrivAccess userAccess{};
+    ipmi::ChannelAccess chAccess{};
+    // TODO Replace with proper calls.
+    uint8_t chNum = static_cast<uint8_t>(ipmi::EChannelID::chanLan1);
+    // Get channel based access information
+    if ((ipmi::ipmiUserGetPrivilegeAccess(userId, chNum, userAccess) !=
+         IPMI_CC_OK) ||
+        (ipmi::getChannelAccessData(chNum, chAccess) != IPMI_CC_OK))
+    {
+        response->rmcpStatusCode =
+            static_cast<uint8_t>(RAKP_ReturnCode::INACTIVE_ROLE);
+        return outPayload;
+    }
+    session->chNum = chNum;
+    // minimum privilege of Channel / User / requested has to be used
+    // as session current privilege level
+    uint8_t minPriv = 0;
+    if (chAccess.privLimit < userAccess.privilege)
+    {
+        minPriv = chAccess.privLimit;
+    }
+    else
+    {
+        minPriv = userAccess.privilege;
+    }
+    if (session->curPrivLevel > static_cast<session::Privilege>(minPriv))
+    {
+        session->curPrivLevel = static_cast<session::Privilege>(minPriv);
+    }
+
+    std::fill(authAlgo->userKey.data(),
+              authAlgo->userKey.data() + authAlgo->userKey.size(), 0);
+    std::copy_n(passwd.c_str(), passwd.size(), authAlgo->userKey.data());
+
     // Copy the Managed System Random Number to the Authentication Algorithm
     std::copy_n(iter, cipher::rakp_auth::BMC_RANDOM_NUMBER_LEN,
                 authAlgo->bmcRandomNum.begin());
@@ -139,15 +206,10 @@
     std::advance(iter, BMC_GUID_LEN);
 
     // Requested Privilege Level
-    session->curPrivLevel =
-        static_cast<session::Privilege>(request->req_max_privilege_level);
     std::copy_n(&(request->req_max_privilege_level),
                 sizeof(request->req_max_privilege_level), iter);
     std::advance(iter, sizeof(request->req_max_privilege_level));
 
-    // Set Max Privilege to ADMIN
-    session->maxPrivLevel = session::Privilege::ADMIN;
-
     // User Name Length Byte
     std::copy_n(&(request->user_name_len), sizeof(request->user_name_len),
                 iter);
diff --git a/command/rakp12.hpp b/command/rakp12.hpp
index 1ce533a..95124be 100644
--- a/command/rakp12.hpp
+++ b/command/rakp12.hpp
@@ -10,6 +10,9 @@
 
 constexpr size_t userNameMaxLen = 16;
 
+constexpr uint8_t userNameOnlyLookupMask = 0x10;
+constexpr uint8_t userNameOnlyLookup = 0x10;
+
 /**
  * @struct RAKP1request
  *
diff --git a/command/rakp34.cpp b/command/rakp34.cpp
index 84c90fc..a89e750 100644
--- a/command/rakp34.cpp
+++ b/command/rakp34.cpp
@@ -123,7 +123,7 @@
     auto rcSessionID = endian::to_ipmi(session->getRCSessionID());
 
     // Session Privilege Level
-    auto sessPrivLevel = static_cast<uint8_t>(session->curPrivLevel);
+    auto sessPrivLevel = session->reqMaxPrivLevel;
 
     // User Name Length Byte
     auto userLength = static_cast<uint8_t>(session->userName.size());
diff --git a/command/session_cmds.cpp b/command/session_cmds.cpp
index d363c1e..5c74d28 100644
--- a/command/session_cmds.cpp
+++ b/command/session_cmds.cpp
@@ -6,6 +6,8 @@
 #include <host-ipmid/ipmid-api.h>
 
 #include <iostream>
+#include <user_channel/channel_layer.hpp>
+#include <user_channel/user_layer.hpp>
 
 namespace command
 {
@@ -29,18 +31,54 @@
     if (reqPrivilegeLevel == 0) // Just return present privilege level
     {
         response->newPrivLevel = static_cast<uint8_t>(session->curPrivLevel);
+        return outPayload;
     }
-    else if (reqPrivilegeLevel <= static_cast<uint8_t>(session->maxPrivLevel))
+    if (reqPrivilegeLevel >
+        (session->reqMaxPrivLevel & session::reqMaxPrivMask))
     {
-        session->curPrivLevel =
-            static_cast<session::Privilege>(reqPrivilegeLevel);
-        response->newPrivLevel = reqPrivilegeLevel;
+        // Requested level exceeds Channel and/or User Privilege Limit
+        response->completionCode = IPMI_CC_EXCEEDS_USER_PRIV;
+        return outPayload;
+    }
+
+    uint8_t userId = ipmi::ipmiUserGetUserId(session->userName);
+    if (userId == ipmi::invalidUserId)
+    {
+        response->completionCode = IPMI_CC_UNSPECIFIED_ERROR;
+        return outPayload;
+    }
+    ipmi::PrivAccess userAccess{};
+    ipmi::ChannelAccess chAccess{};
+    if ((ipmi::ipmiUserGetPrivilegeAccess(userId, session->chNum, userAccess) !=
+         IPMI_CC_OK) ||
+        (ipmi::getChannelAccessData(session->chNum, chAccess) != IPMI_CC_OK))
+    {
+        response->completionCode = IPMI_CC_INVALID_PRIV_LEVEL;
+        return outPayload;
+    }
+    // Use the minimum privilege of user or channel
+    uint8_t minPriv = 0;
+    if (chAccess.privLimit < userAccess.privilege)
+    {
+        minPriv = chAccess.privLimit;
     }
     else
     {
+        minPriv = userAccess.privilege;
+    }
+    if (reqPrivilegeLevel > minPriv)
+    {
         // Requested level exceeds Channel and/or User Privilege Limit
         response->completionCode = IPMI_CC_EXCEEDS_USER_PRIV;
     }
+    else
+    {
+        // update current privilege of the session.
+        session->curPrivLevel =
+            static_cast<session::Privilege>(reqPrivilegeLevel);
+        response->newPrivLevel = reqPrivilegeLevel;
+    }
+
     return outPayload;
 }
 
diff --git a/session.hpp b/session.hpp
index f3dc2ae..bf136e3 100644
--- a/session.hpp
+++ b/session.hpp
@@ -43,6 +43,10 @@
 // Seconds of inactivity allowed when session is active
 constexpr auto SESSION_INACTIVITY_TIMEOUT = 60s;
 
+// Mask to get only the privilege from requested maximum privlege (RAKP message
+// 1)
+constexpr uint8_t reqMaxPrivMask = 0xF;
+
 /**
  * @struct SequenceNumbers Session Sequence Numbers
  *
@@ -261,12 +265,12 @@
     /**
      * @brief Session's Current Privilege Level
      */
-    Privilege curPrivLevel;
+    Privilege curPrivLevel = Privilege::CALLBACK;
 
     /**
-     * @brief Session's Maximum Privilege Level
+     * @brief Session's Requested Maximum Privilege Level
      */
-    Privilege maxPrivLevel = Privilege::CALLBACK;
+    uint8_t reqMaxPrivLevel;
 
     SequenceNumbers sequenceNums;  // Session Sequence Numbers
     State state = State::INACTIVE; // Session State
@@ -274,6 +278,7 @@
 
     /** @brief Socket channel for communicating with the remote client.*/
     std::shared_ptr<udpsocket::Channel> channelPtr;
+    uint8_t chNum;
 
   private:
     SessionID bmcSessionID = 0;           // BMC Session ID