src/platform/MpiServer.c (548 lines of code) (raw):
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#include <PlatformCommon.h>
#include <MpiServer.h>
#include <ModulesManager.h>
// 500 milliseconds
#define MPI_WORKER_SLEEP 500
#define MAX_CONTENTLENGTH_LENGTH 16
#define MAX_ERROR_LENGTH 16
#define MAX_QUEUED_CONNECTIONS 5
#define MAX_REASONSTRING_LENGTH 32
#define MAX_STATUS_CODE_LENGTH 3
#define MODULES_BIN_PATH "/usr/lib/osconfig"
#define CONFIG_JSON_PATH "/etc/osconfig/osconfig.json"
static const char* g_socketPrefix = "/run/osconfig";
static const char* g_mpiSocket = "/run/osconfig/mpid.sock";
static const char* g_clientName = "ClientName";
static const char* g_maxPayloadSizeBytes = "MaxPayloadSizeBytes";
static const char* g_clientSession = "ClientSession";
static const char* g_componentName = "ComponentName";
static const char* g_objectName = "ObjectName";
static const char* g_payload = "Payload";
static int g_socketfd = -1;
static struct sockaddr_un g_socketaddr = {0};
static socklen_t g_socketlen = 0;
static pthread_t g_mpiServerWorker = 0;
static int g_mpiServerWorkerError = -1;
static bool g_serverActive = false;
char g_mpiCall[MPI_CALL_MESSAGE_LENGTH] = {0};
static const char g_mpiCallObjectTemplate[] = " during %s to %s.%s\n";
static const char g_mpiCallModelTemplate[] = " during %s\n";
static MPI_HANDLE CallMpiOpen(const char* clientName, const unsigned int maxPayloadSizeBytes)
{
MPI_HANDLE handle = NULL;
if (NULL == (handle = MpiOpen(clientName, maxPayloadSizeBytes)))
{
OsConfigLogError(GetPlatformLog(), "MpiOpen failed to create a session for client '%s'", clientName);
}
return handle;
}
static void CallMpiClose(MPI_HANDLE handle)
{
OsConfigLogDebug(GetPlatformLog(), "Received MpiClose request, session %p ('%s')", handle, (char*)handle);
MpiClose((MPI_HANDLE)handle);
}
static int CallMpiSet(MPI_HANDLE handle, const char* componentName, const char* objectName, MPI_JSON_STRING payload, const int payloadSize)
{
int status = MPI_OK;
snprintf(g_mpiCall, sizeof(g_mpiCall), g_mpiCallObjectTemplate, MPI_SET_URI, componentName, objectName);
status = MpiSet((MPI_HANDLE)handle, componentName, objectName, payload, payloadSize);
if (IsDebugLoggingEnabled())
{
if (MPI_OK == status)
{
OsConfigLogDebug(GetPlatformLog(), "MpiSet(%s, %s) request, session %p ('%s')", componentName, objectName, handle, (char*)handle);
}
else
{
OsConfigLogError(GetPlatformLog(), "MpiSet(%s, %s) request, session %p ('%s'), failed: %d", componentName, objectName, handle, (char*)handle, status);
}
}
memset(g_mpiCall, 0, sizeof(g_mpiCall));
return status;
}
static int CallMpiGet(MPI_HANDLE handle, const char* componentName, const char* objectName, MPI_JSON_STRING* payload, int* payloadSize)
{
int status = MPI_OK;
snprintf(g_mpiCall, sizeof(g_mpiCall), g_mpiCallObjectTemplate, MPI_GET_URI, componentName, objectName);
status = MpiGet((MPI_HANDLE)handle, componentName, objectName, payload, payloadSize);
if (IsDebugLoggingEnabled())
{
if (MPI_OK == status)
{
OsConfigLogDebug(GetPlatformLog(), "MpiGet(%s, %s) request, session %p ('%s')", componentName, objectName, handle, (char*)handle);
}
else
{
OsConfigLogError(GetPlatformLog(), "MpiGet(%s, %s) request, session %p ('%s'), failed: %d", componentName, objectName, handle, (char*)handle, status);
}
}
memset(g_mpiCall, 0, sizeof(g_mpiCall));
return status;
}
static int CallMpiSetDesired(MPI_HANDLE handle, const MPI_JSON_STRING payload, const int payloadSize)
{
int status = MPI_OK;
snprintf(g_mpiCall, sizeof(g_mpiCall), g_mpiCallModelTemplate, MPI_SET_DESIRED_URI);
status = MpiSetDesired((MPI_HANDLE)handle, payload, payloadSize);
if (IsDebugLoggingEnabled())
{
if (MPI_OK == status)
{
OsConfigLogDebug(GetPlatformLog(), "MpiSetDesired request, session %p ('%s')", handle, (char*)handle);
}
else
{
OsConfigLogError(GetPlatformLog(), "MpiSetDesired request, session %p ('%s'), failed: %d", handle, (char*)handle, status);
}
}
memset(g_mpiCall, 0, sizeof(g_mpiCall));
return status;
}
static int CallMpiGetReported(MPI_HANDLE handle, MPI_JSON_STRING* payload, int* payloadSize)
{
int status = MPI_OK;
snprintf(g_mpiCall, sizeof(g_mpiCall), g_mpiCallModelTemplate, MPI_GET_REPORTED_URI);
status = MpiGetReported((MPI_HANDLE)handle, payload, payloadSize);
if (IsDebugLoggingEnabled())
{
if (MPI_OK == status)
{
OsConfigLogDebug(GetPlatformLog(), "MpiGetReported request, session %p ('%s')", handle, (char*)handle);
}
else
{
OsConfigLogError(GetPlatformLog(), "MpiGetReported request, session %p ('%s'), failed: %d", handle, (char*)handle, status);
}
}
memset(g_mpiCall, 0, sizeof(g_mpiCall));
return status;
}
HTTP_STATUS SetErrorResponse(const char* uri, int mpiStatus, char** response, int* responseSize)
{
int size = 0;
const char* errorFormat = "\"%d\"";
HTTP_STATUS status = HTTP_OK;
if (MPI_OK != mpiStatus)
{
status = HTTP_INTERNAL_SERVER_ERROR;
size = strlen(errorFormat) + MAX_ERROR_LENGTH + 1;
if (NULL != (*response = (char*)malloc(size)))
{
snprintf(*response, size, errorFormat, mpiStatus);
*responseSize = strlen(*response);
}
else
{
OsConfigLogError(GetPlatformLog(), "%s: failed to allocate memory for error response", uri);
}
}
return status;
}
HTTP_STATUS HandleMpiCall(const char* uri, const char* requestBody, char** response, int* responseSize, MPI_CALLS handlers)
{
JSON_Value* rootValue = NULL;
JSON_Value* clientValue = NULL;
JSON_Value* componentValue = NULL;
JSON_Value* objectValue = NULL;
JSON_Value* payloadValue = NULL;
JSON_Value* maxPayloadSizeValue = NULL;
JSON_Object* rootObject = NULL;
int mpiStatus = MPI_OK;
char* uuid = NULL;
const char* client = NULL;
const char* component = NULL;
const char* object = NULL;
char* payload = NULL;
int maxPayloadSizeBytes = 0;
int estimatedSize = 0;
const char* responseFormat = "\"%s\"";
HTTP_STATUS status = HTTP_OK;
if (NULL == uri)
{
OsConfigLogError(GetPlatformLog(), "HandleMpiCall: called with invalid null URI");
status = HTTP_BAD_REQUEST;
}
else if (NULL == requestBody)
{
OsConfigLogError(GetPlatformLog(), "HandleMpiCall(%s): called with invalid null request body", uri);
status = HTTP_BAD_REQUEST;
}
else if (NULL == response)
{
OsConfigLogError(GetPlatformLog(), "HandleMpiCall(%s): called with invalid null response", uri);
status = HTTP_BAD_REQUEST;
}
else if (NULL == responseSize)
{
OsConfigLogError(GetPlatformLog(), "HandleMpiCall(%s): called with invalid null response size", uri);
status = HTTP_BAD_REQUEST;
}
else if (NULL == (rootValue = json_parse_string(requestBody)))
{
OsConfigLogError(GetPlatformLog(), "HandleMpiCall(%s): failed to parse request body", uri);
status = HTTP_BAD_REQUEST;
}
else if (NULL == (rootObject = json_value_get_object(rootValue)))
{
OsConfigLogError(GetPlatformLog(), "HandleMpiCall(%s): failed to get object from request body", uri);
status = HTTP_BAD_REQUEST;
}
else
{
if (0 == strcmp(uri, MPI_OPEN_URI))
{
if (NULL == (clientValue = json_object_get_value(rootObject, g_clientName)))
{
OsConfigLogError(GetPlatformLog(), "%s: failed to parse '%s' from request body", uri, g_clientName);
status = HTTP_BAD_REQUEST;
}
else if (JSONString != json_value_get_type(clientValue))
{
OsConfigLogError(GetPlatformLog(), "%s: '%s' is not a string", uri, g_clientName);
status = HTTP_BAD_REQUEST;
}
else if (NULL == (client = json_value_get_string(clientValue)))
{
OsConfigLogError(GetPlatformLog(), "%s: failed to get string from '%s'", uri, g_clientName);
status = HTTP_BAD_REQUEST;
}
else if (NULL == (maxPayloadSizeValue = json_object_get_value(rootObject, g_maxPayloadSizeBytes)))
{
OsConfigLogError(GetPlatformLog(), "%s: failed to parse '%s' from request body", uri, g_maxPayloadSizeBytes);
status = HTTP_BAD_REQUEST;
}
else if (JSONNumber != json_value_get_type(maxPayloadSizeValue))
{
OsConfigLogError(GetPlatformLog(), "%s: '%s' is not a number", uri, g_maxPayloadSizeBytes);
status = HTTP_BAD_REQUEST;
}
else if (0 > (maxPayloadSizeBytes = (int)json_value_get_number(maxPayloadSizeValue)))
{
OsConfigLogError(GetPlatformLog(), "%s: '%s' is negative: %d", uri, g_maxPayloadSizeBytes, maxPayloadSizeBytes);
status = HTTP_BAD_REQUEST;
}
else
{
uuid = (char*)handlers.mpiOpen(client, maxPayloadSizeBytes);
if (uuid)
{
estimatedSize = strlen(responseFormat) + strlen(uuid) + 1;
if (NULL != (*response = (char*)malloc(estimatedSize)))
{
snprintf(*response, estimatedSize, responseFormat, uuid);
*responseSize = strlen(*response);
}
else
{
OsConfigLogError(GetPlatformLog(), "%s: failed to allocate memory for response", uri);
status = HTTP_INTERNAL_SERVER_ERROR;
}
FREE_MEMORY(uuid);
}
else
{
OsConfigLogError(GetPlatformLog(), "%s: failed to open client '%s'", uri, client);
status = HTTP_INTERNAL_SERVER_ERROR;
}
}
}
else if ((0 == strcmp(uri, MPI_CLOSE_URI)) ||
(0 == strcmp(uri, MPI_SET_URI)) ||
(0 == strcmp(uri, MPI_GET_URI)) ||
(0 == strcmp(uri, MPI_SET_DESIRED_URI)) ||
(0 == strcmp(uri, MPI_GET_REPORTED_URI)))
{
if (NULL == (clientValue = json_object_get_value(rootObject, g_clientSession)))
{
OsConfigLogError(GetPlatformLog(), "%s: failed to parse '%s' from request body", uri, g_clientSession);
status = HTTP_BAD_REQUEST;
}
else if (JSONString != json_value_get_type(clientValue))
{
OsConfigLogError(GetPlatformLog(), "%s: '%s' is not a string", uri, g_clientSession);
status = HTTP_BAD_REQUEST;
}
else if (NULL == (client = json_value_get_string(clientValue)))
{
OsConfigLogError(GetPlatformLog(), "%s: failed to get string from '%s'", uri, g_clientSession);
status = HTTP_BAD_REQUEST;
}
else if (0 == strcmp(uri, MPI_CLOSE_URI))
{
handlers.mpiClose((MPI_HANDLE)client);
status = HTTP_OK;
}
else if ((0 == strcmp(uri, MPI_SET_URI)) || (0 == strcmp(uri, MPI_GET_URI)))
{
if (NULL == (componentValue = json_object_get_value(rootObject, g_componentName)))
{
OsConfigLogError(GetPlatformLog(), "%s: failed to parse '%s' from request body", uri, g_componentName);
status = HTTP_BAD_REQUEST;
}
else if (JSONString != json_value_get_type(componentValue))
{
OsConfigLogError(GetPlatformLog(), "%s: '%s' is not a string", uri, g_componentName);
status = HTTP_BAD_REQUEST;
}
else if (NULL == (component = json_value_get_string(componentValue)))
{
OsConfigLogError(GetPlatformLog(), "%s: failed to get string from '%s'", uri, g_componentName);
status = HTTP_BAD_REQUEST;
}
else if (NULL == (objectValue = json_object_get_value(rootObject, g_objectName)))
{
OsConfigLogError(GetPlatformLog(), "%s: failed to parse '%s' from request body", uri, g_objectName);
status = HTTP_BAD_REQUEST;
}
else if (JSONString != json_value_get_type(objectValue))
{
OsConfigLogError(GetPlatformLog(), "%s: '%s' is not a string", uri, g_objectName);
status = HTTP_BAD_REQUEST;
}
else if (NULL == (object = json_value_get_string(objectValue)))
{
OsConfigLogError(GetPlatformLog(), "%s: failed to get string from '%s'", uri, g_objectName);
status = HTTP_BAD_REQUEST;
}
else
{
if (0 == strcmp(uri, MPI_SET_URI))
{
if (NULL == (payloadValue = json_object_get_value(rootObject, g_payload)))
{
OsConfigLogError(GetPlatformLog(), "%s: failed to parse '%s' from request body", uri, g_payload);
status = HTTP_BAD_REQUEST;
}
else if (NULL == (payload = json_serialize_to_string(payloadValue)))
{
OsConfigLogError(GetPlatformLog(), "%s: failed to get payload string", uri);
status = HTTP_BAD_REQUEST;
}
else
{
if (MPI_OK != (mpiStatus = handlers.mpiSet((MPI_HANDLE)client, component, object, (MPI_JSON_STRING)payload, strlen(payload))))
{
status = SetErrorResponse(uri, mpiStatus, response, responseSize);
if (IsDebugLoggingEnabled())
{
OsConfigLogError(GetPlatformLog(), "%s(%s, %s): failed for client '%s' with %d (returning %d)", uri, component, object, client, mpiStatus, status);
}
}
}
}
else if (MPI_OK != (mpiStatus = handlers.mpiGet((MPI_HANDLE)client, component, object, response, responseSize)))
{
status = SetErrorResponse(uri, mpiStatus, response, responseSize);
OsConfigLogDebug(GetPlatformLog(), "%s(%s, %s): failed for client '%s' with %d (returning %d)", uri, component, object, client, mpiStatus, status);
}
}
}
else if (0 == strcmp(uri, MPI_SET_DESIRED_URI))
{
if (NULL == (payloadValue = json_object_get_value(rootObject, g_payload)))
{
OsConfigLogError(GetPlatformLog(), "%s: failed to parse '%s' from request body", uri, g_payload);
status = HTTP_BAD_REQUEST;
}
else if (NULL == (payload = json_serialize_to_string(payloadValue)))
{
OsConfigLogError(GetPlatformLog(), "%s: failed to get payload string", uri);
status = HTTP_BAD_REQUEST;
}
else if (MPI_OK != (mpiStatus = handlers.mpiSetDesired((MPI_HANDLE)client, (MPI_JSON_STRING)payload, strlen(payload))))
{
OsConfigLogError(GetPlatformLog(), "%s: failed for client '%s' with %d (returning %d)", uri, client, mpiStatus, status);
status = SetErrorResponse(uri, mpiStatus, response, responseSize);
}
}
else if (0 == strcmp(uri, MPI_GET_REPORTED_URI))
{
if (MPI_OK != (mpiStatus = handlers.mpiGetReported((MPI_HANDLE)client, response, responseSize)))
{
OsConfigLogError(GetPlatformLog(), "%s: failed for client '%s' with %d (returning %d)", uri, client, mpiStatus, status);
status = SetErrorResponse(uri, mpiStatus, response, responseSize);
}
}
}
else
{
OsConfigLogError(GetPlatformLog(), "%s: invalid request URI", uri);
status = HTTP_NOT_FOUND;
}
}
json_free_serialized_string(payload);
json_value_free(rootValue);
return status;
}
static char* HttpReasonAsString(HTTP_STATUS statusCode)
{
char* reason = NULL;
if (NULL != (reason = (char*)malloc(MAX_REASONSTRING_LENGTH)))
{
switch (statusCode)
{
case HTTP_OK:
strcpy(reason, "OK");
break;
case HTTP_BAD_REQUEST:
strcpy(reason, "Bad Request");
break;
case HTTP_NOT_FOUND:
strcpy(reason, "Not Found");
break;
case HTTP_INTERNAL_SERVER_ERROR:
strcpy(reason, "Internal Server Error");
break;
default:
strcpy(reason, "Unknown");
}
}
return reason;
}
static void* MpiServerWorker(void* arguments)
{
const char* responseFormat = "HTTP/1.1 %d %s\r\nServer: OSConfig\r\nContent-Type: application/json\r\nContent-Length: %d\r\n\r\n%.*s";
int socketHandle = -1;
char* uri = NULL;
int contentLength = 0;
char* requestBody = NULL;
HTTP_STATUS status = HTTP_OK;
char* httpReason = NULL;
char* responseBody = NULL;
int responseSize = 0;
char* buffer = NULL;
int estimatedSize = 0;
int actualSize = 0;
ssize_t bytes = 0;
MPI_CALLS mpiCalls = {
CallMpiOpen,
CallMpiClose,
CallMpiSet,
CallMpiGet,
CallMpiSetDesired,
CallMpiGetReported
};
UNUSED(arguments);
while (g_serverActive)
{
status = HTTP_OK;
if (0 <= (socketHandle = accept(g_socketfd, (struct sockaddr*)&g_socketaddr, &g_socketlen)))
{
AreModulesLoadedAndLoadIfNot(MODULES_BIN_PATH, CONFIG_JSON_PATH);
OsConfigLogDebug(GetPlatformLog(), "Accepted connection: path %s, handle '%d'", g_socketaddr.sun_path, socketHandle);
if (NULL == (uri = ReadUriFromSocket(socketHandle, GetPlatformLog())))
{
OsConfigLogError(GetPlatformLog(), "Failed to read request URI %d", socketHandle);
status = HTTP_BAD_REQUEST;
}
else if (0 != (contentLength = ReadHttpContentLengthFromSocket(socketHandle, GetPlatformLog())))
{
if (NULL == (requestBody = (char*)malloc(contentLength + 1)))
{
OsConfigLogError(GetPlatformLog(), "%s: failed to allocate memory for HTTP body, Content-Length %d", uri, contentLength);
status = HTTP_BAD_REQUEST;
}
else
{
memset(requestBody, 0, contentLength + 1);
if (contentLength != (int)(bytes = read(socketHandle, requestBody, contentLength)))
{
OsConfigLogError(GetPlatformLog(), "%s: failed to read complete HTTP body, Content-Length %d, bytes read %d", uri, contentLength, (int)bytes);
status = HTTP_BAD_REQUEST;
}
}
}
if (HTTP_OK == status)
{
OsConfigLogDebug(GetPlatformLog(), "%s: content-length %d, body, '%s'", uri, contentLength, requestBody);
status = HandleMpiCall(uri, requestBody, &responseBody, &responseSize, mpiCalls);
}
httpReason = HttpReasonAsString(status);
estimatedSize = strlen(responseFormat) + MAX_STATUS_CODE_LENGTH + strlen(httpReason) + MAX_CONTENTLENGTH_LENGTH + responseSize + 1;
if (NULL != (buffer = (char*)malloc(estimatedSize)))
{
memset(buffer, 0, estimatedSize);
snprintf(buffer, estimatedSize, responseFormat, (int)status, httpReason, responseSize, responseSize, (responseBody ? responseBody : ""));
actualSize = (int)strlen(buffer);
if (actualSize != (bytes = write(socketHandle, buffer, actualSize)))
{
OsConfigLogError(GetPlatformLog(), "%s: failed to write complete HTTP response, %d bytes of %d", uri, (int)bytes, actualSize);
}
}
else
{
OsConfigLogError(GetPlatformLog(), "%s: failed to allocate memory for HTTP response, %d bytes of %d", uri, 0, estimatedSize);
}
if (0 != close(socketHandle))
{
OsConfigLogError(GetPlatformLog(), "Failed to close socket: path %s, handle '%d'", g_socketaddr.sun_path, socketHandle);
}
OsConfigLogDebug(GetPlatformLog(), "Closed connection: path %s, handle '%d'", g_socketaddr.sun_path, socketHandle);
contentLength = 0;
responseSize = 0;
FREE_MEMORY(requestBody);
FREE_MEMORY(responseBody);
FREE_MEMORY(httpReason);
FREE_MEMORY(buffer);
FREE_MEMORY(uri);
SleepMilliseconds(MPI_WORKER_SLEEP);
}
}
return NULL;
}
void MpiInitialize(void)
{
struct stat st;
if (-1 == stat(g_socketPrefix, &st))
{
// S_IRUSR (0x00400): Read permission, owner
// S_IWUSR (0x00200): Write permission, owner
// S_IXUSR (0x00100): Execute/search permission, owner
if (0 != mkdir(g_socketPrefix, S_IRUSR | S_IWUSR | S_IXUSR))
{
OsConfigLogError(GetPlatformLog(), "Failed to create socket '%s'", g_socketPrefix);
}
}
if (0 <= (g_socketfd = socket(AF_UNIX, SOCK_STREAM, 0)))
{
memset(&g_socketaddr, 0, sizeof(g_socketaddr));
g_socketaddr.sun_family = AF_UNIX;
strncpy(g_socketaddr.sun_path, g_mpiSocket, sizeof(g_socketaddr.sun_path) - 1);
g_socketlen = sizeof(g_socketaddr);
unlink(g_mpiSocket);
if (0 == bind(g_socketfd, (struct sockaddr*)&g_socketaddr, g_socketlen))
{
RestrictFileAccessToCurrentAccountOnly(g_mpiSocket);
if (0 == listen(g_socketfd, MAX_QUEUED_CONNECTIONS))
{
OsConfigLogInfo(GetPlatformLog(), "Listening on socket '%s'", g_mpiSocket);
g_serverActive = true;
g_mpiServerWorkerError = pthread_create(&g_mpiServerWorker, NULL, MpiServerWorker, NULL);
}
else
{
OsConfigLogError(GetPlatformLog(), "Failed to listen on socket '%s'", g_mpiSocket);
}
}
else
{
OsConfigLogError(GetPlatformLog(), "Failed to bind socket '%s'", g_mpiSocket);
}
}
else
{
OsConfigLogError(GetPlatformLog(), "Failed to create socket '%s'", g_mpiSocket);
}
}
void MpiShutdown(void)
{
g_serverActive = false;
if (g_mpiServerWorkerError == 0)
{
pthread_join(g_mpiServerWorker, NULL);
g_mpiServerWorkerError = -1;
}
UnloadModules();
close(g_socketfd);
unlink(g_mpiSocket);
}
void MpiDoWork(void)
{
return;
}