diff options
Diffstat (limited to 'metis/ccnx/forwarder/metis/config/metis_CommandLineInterface.c')
-rw-r--r-- | metis/ccnx/forwarder/metis/config/metis_CommandLineInterface.c | 405 |
1 files changed, 405 insertions, 0 deletions
diff --git a/metis/ccnx/forwarder/metis/config/metis_CommandLineInterface.c b/metis/ccnx/forwarder/metis/config/metis_CommandLineInterface.c new file mode 100644 index 00000000..10857a29 --- /dev/null +++ b/metis/ccnx/forwarder/metis/config/metis_CommandLineInterface.c @@ -0,0 +1,405 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NB: binds to all interfaces on the listen port, which might be a security issue. + * + * The CLI runs as an event managed listener. The Api here creates, starts, stops, and destroys it. + * + * The CLI is a user interface to the programmatic interface in <code>metis_Configuration.h</code> + * + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <arpa/inet.h> + +#include <LongBow/runtime.h> + +#include <parc/algol/parc_Memory.h> +#include <parc/algol/parc_ArrayList.h> + +#include "metis_CommandLineInterface.h" + +struct metis_command_line_interface { + MetisForwarder *metis; + PARCEventSocket *listener; + PARCArrayList *openSessions; + + uint16_t port; +}; + +typedef struct metis_cli_session { + MetisCommandLineInterface *parentCli; + MetisSocketType clientSocket; + struct sockaddr *clientAddress; + int clientAddressLength; + PARCEventQueue *streamBuffer; + bool doingTheRightThing; +} _MetisCommandLineInterface_Session; + +struct metis_cli_command; +typedef struct metis_cli_command _MetisCommandLineInterface_Command; + +struct metis_cli_command { + char *text; + char *helpDescription; + void (*func)(_MetisCommandLineInterface_Session *session, _MetisCommandLineInterface_Command *command, const char *params); +}; + +static void _metisCommandLineInterface_ListenerCallback(MetisSocketType client_socket, + struct sockaddr *client_addr, int socklen, void *user_data); + +static _MetisCommandLineInterface_Session *metisCliSession_Create(MetisCommandLineInterface *cli, MetisSocketType client_socket, struct sockaddr *client_addr, int socklen); +static void _metisCliSession_Destory(_MetisCommandLineInterface_Session **cliSessionPtr); +static void _metisCliSession_ReadCallback(PARCEventQueue *event, PARCEventType type, void *cliSessionVoid); +static void _metisCliSession_EventCallback(PARCEventQueue *event, PARCEventQueueEventType what, void *cliSessionVoid); +static bool _metisCliSession_ProcessCommand(_MetisCommandLineInterface_Session *session, char *cmdline); +static void _metisCliSession_DisplayMotd(_MetisCommandLineInterface_Session *session); +static void _metisCliSession_DisplayPrompt(_MetisCommandLineInterface_Session *session); + +// used by PARCArrayList +static void +_session_VoidDestroyer(void **cliSessionVoidPtr) +{ + _MetisCommandLineInterface_Session **cliSessionPtr = (_MetisCommandLineInterface_Session **) cliSessionVoidPtr; + (*cliSessionPtr)->doingTheRightThing = true; + _metisCliSession_Destory(cliSessionPtr); +} + +// ==================================================================================== + +static void _cmd_Help(_MetisCommandLineInterface_Session *session, _MetisCommandLineInterface_Command *command, const char *params); +static void _cmd_Show(_MetisCommandLineInterface_Session *session, _MetisCommandLineInterface_Command *command, const char *params); +static void _cmd_Exit(_MetisCommandLineInterface_Session *session, _MetisCommandLineInterface_Command *command, const char *params); +static void _cmd_Tunnel(_MetisCommandLineInterface_Session *session, _MetisCommandLineInterface_Command *command, const char *params); +static void _cmd_Version(_MetisCommandLineInterface_Session *session, _MetisCommandLineInterface_Command *command, const char *params); + +/** + * @typedef _cliCommands + * The commands, their short help, and their function pointer + * @constant <#name#> <#description#> + * List must be terminated with a NULL entry + * + * Example: + * @code + * <#example#> + * @endcode + */ +static _MetisCommandLineInterface_Command _cliCommands[] = { + { "exit", "Ends the session", _cmd_Exit }, + { "help", "Displays the help menu", _cmd_Help }, + { "show", "Displays state", _cmd_Show }, + { "tunnel", "manage tunnels", _cmd_Tunnel }, + { "ver", "Forwarder version", _cmd_Version }, + { NULL, NULL, NULL } +}; + +// ==================================================================================== + +MetisCommandLineInterface * +metisCommandLineInterface_Create(MetisForwarder *metis, uint16_t port) +{ + MetisCommandLineInterface *cli = parcMemory_AllocateAndClear(sizeof(MetisCommandLineInterface)); + assertNotNull(cli, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(MetisCommandLineInterface)); + cli->port = port; + cli->listener = NULL; + cli->metis = metis; + cli->openSessions = parcArrayList_Create(_session_VoidDestroyer); + + return cli; +} + +void +metisCommandLineInterface_Start(MetisCommandLineInterface *cli) +{ + // listen address + struct sockaddr_in6 addr6; + memset(&addr6, 0, sizeof(addr6)); + addr6.sin6_family = PF_INET6; + addr6.sin6_port = htons(cli->port); + + MetisDispatcher *dispatcher = metisForwarder_GetDispatcher(cli->metis); + PARCEventSocket *listener = metisDispatcher_CreateListener(dispatcher, _metisCommandLineInterface_ListenerCallback, cli, -1, (struct sockaddr *) &addr6, sizeof(addr6)); + assertNotNull(listener, "Got null listener"); + + cli->listener = listener; +} + +void +metisCommandLineInterface_Destroy(MetisCommandLineInterface **cliPtr) +{ + assertNotNull(cliPtr, "Parameter must be non-null double pointer"); + assertNotNull(*cliPtr, "Parameter must dereference to non-null pointer"); + + MetisCommandLineInterface *cli = *cliPtr; + + parcArrayList_Destroy(&cli->openSessions); + + if (cli->listener) { + MetisDispatcher *dispatcher = metisForwarder_GetDispatcher(cli->metis); + metisDispatcher_DestroyListener(dispatcher, &(cli->listener)); + } + + parcMemory_Deallocate((void **) &cli); + *cliPtr = NULL; +} + +/** + * Creates a client-specific session + * + * <#Discussion#> + * + * @param <#param1#> + * @return <#return#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +static _MetisCommandLineInterface_Session * +metisCliSession_Create(MetisCommandLineInterface *cli, MetisSocketType clientSocket, struct sockaddr *clientAddress, int clientAddressLength) +{ + _MetisCommandLineInterface_Session *session = parcMemory_AllocateAndClear(sizeof(_MetisCommandLineInterface_Session)); + assertNotNull(session, "parcMemory_AllocateAndClear(%zu) returned NULL", sizeof(_MetisCommandLineInterface_Session)); + session->parentCli = cli; + session->clientAddress = parcMemory_Allocate(clientAddressLength); + assertNotNull(session->clientAddress, "parcMemory_Allocate(%d) returned NULL", clientAddressLength); + session->clientAddressLength = clientAddressLength; + session->clientSocket = clientSocket; + + memcpy(session->clientAddress, clientAddress, clientAddressLength); + + MetisDispatcher *dispatcher = metisForwarder_GetDispatcher(cli->metis); + PARCEventScheduler *eventBase = metisDispatcher_GetEventScheduler(dispatcher); + session->streamBuffer = parcEventQueue_Create(eventBase, clientSocket, PARCEventQueueOption_CloseOnFree | PARCEventQueueOption_DeferCallbacks); + + parcEventQueue_SetCallbacks(session->streamBuffer, _metisCliSession_ReadCallback, NULL, _metisCliSession_EventCallback, session); + parcEventQueue_Enable(session->streamBuffer, PARCEventType_Read); + + return session; +} + +/** + * SHOULD ONLY BE CALLED FROM ARRAYLIST + * + * Do not call this on your own!! It should only be called when an + * item is removed from the cli->openSessions array list. + * + * Will close the tcp session and free memory. + * + * @param <#param1#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +static void +_metisCliSession_Destory(_MetisCommandLineInterface_Session **cliSessionPtr) +{ + assertNotNull(cliSessionPtr, "Parameter must be non-null double pointer"); + assertNotNull(*cliSessionPtr, "Parameter must dereference to non-null pointer"); + _MetisCommandLineInterface_Session *session = *cliSessionPtr; + + assertTrue(session->doingTheRightThing, "Ha! caught you! You called Destroy outside the PARCArrayList"); + + parcEventQueue_Destroy(&(session->streamBuffer)); + parcMemory_Deallocate((void **) &(session->clientAddress)); + parcMemory_Deallocate((void **) &session); + *cliSessionPtr = NULL; +} + +/** + * Called on a new connection to the server socket + * + * Will allocate a new _MetisCommandLineInterface_Session and put it in the + * server's PARCArrayList + * + * @param <#param1#> + * @return <#return#> + * + * Example: + * @code + * <#example#> + * @endcode + */ +static void +_metisCommandLineInterface_ListenerCallback(MetisSocketType client_socket, + struct sockaddr *client_addr, int socklen, void *user_data) +{ + MetisCommandLineInterface *cli = (MetisCommandLineInterface *) user_data; + _MetisCommandLineInterface_Session *session = metisCliSession_Create(cli, client_socket, client_addr, socklen); + parcArrayList_Add(cli->openSessions, session); + + _metisCliSession_DisplayMotd(session); + _metisCliSession_DisplayPrompt(session); +} + +static void +_metisCliSession_ReadCallback(PARCEventQueue *event, PARCEventType type, void *cliSessionVoid) +{ + assertTrue(type == PARCEventType_Read, "Illegal type: expected read event, got %d\n", type); + _MetisCommandLineInterface_Session *session = (_MetisCommandLineInterface_Session *) cliSessionVoid; + PARCEventBuffer *input = parcEventBuffer_GetQueueBufferInput(event); + + while (parcEventBuffer_GetLength(input) > 0) { + size_t readLength = 0; + char *cmdline = parcEventBuffer_ReadLine(input, &readLength); + if (cmdline == NULL) { + // we have run out of input, we're done + parcEventBuffer_Destroy(&input); + return; + } + + // we have a whole command line + bool success = _metisCliSession_ProcessCommand(session, cmdline); + parcEventBuffer_FreeLine(input, &cmdline); + + if (!success) { + // the session is dead + parcEventBuffer_Destroy(&input); + return; + } + + _metisCliSession_DisplayPrompt(session); + } + parcEventBuffer_Destroy(&input); +} + +static void +_metisCommandLineInterface_RemoveSession(MetisCommandLineInterface *cli, _MetisCommandLineInterface_Session *session) +{ + size_t length = parcArrayList_Size(cli->openSessions); + for (size_t i = 0; i < length; i++) { + _MetisCommandLineInterface_Session *x = parcArrayList_Get(cli->openSessions, i); + if (x == session) { + // removing from list will call the session destroyer + parcArrayList_RemoveAndDestroyAtIndex(cli->openSessions, i); + return; + } + } + assertTrue(0, "Illegal state: did not find a session in openSessions %p", (void *) session); +} + +static void +_metisCliSession_EventCallback(PARCEventQueue *event, PARCEventQueueEventType what, void *cliSessionVoid) +{ + _MetisCommandLineInterface_Session *session = (_MetisCommandLineInterface_Session *) cliSessionVoid; + if (what & PARCEventQueueEventType_Error) { + MetisCommandLineInterface *cli = session->parentCli; + _metisCommandLineInterface_RemoveSession(cli, session); + } +} + +static void +_metisCliSession_DisplayMotd(_MetisCommandLineInterface_Session *session) +{ + parcEventQueue_Printf(session->streamBuffer, "Metis Forwarder CLI\n"); + parcEventQueue_Printf(session->streamBuffer, "Copyright (c) 2017 Cisco and/or its affiliates.\n\n"); + + parcEventQueue_Flush(session->streamBuffer, PARCEventType_Write); +} + +static void +_metisCliSession_DisplayPrompt(_MetisCommandLineInterface_Session *session) +{ + parcEventQueue_Printf(session->streamBuffer, "metis> "); + parcEventQueue_Flush(session->streamBuffer, PARCEventType_Write); +} + +/** + * Process commands until there's not a whole line (upto CRLF) + * + * <#Discussion#> + * + * @param <#param1#> + * @return false if the session died, true if its still going + * + * Example: + * @code + * <#example#> + * @endcode + */ +static bool +_metisCliSession_ProcessCommand(_MetisCommandLineInterface_Session *session, char *cmdline) +{ + // parse out the first word. The NULL goes in after a space or tab. + // "cmd" will be the string prior to the NULL, and "cmdline" will be what's after the NULL. + char *cmd = strsep(&cmdline, " \t"); + + // there's a secret command for unit testing + if (strcasecmp(cmd, "~~") == 0) { + parcEventQueue_Printf(session->streamBuffer, "success: %s\n", cmdline); + return true; + } + + for (int i = 0; _cliCommands[i].text != NULL; i++) { + if (strcasecmp(_cliCommands[i].text, cmd) == 0) { + _cliCommands[i].func(session, &_cliCommands[i], cmdline); + if (_cliCommands[i].func == _cmd_Exit) { + return false; + } + return true; + } + } + + // could not find command, print the help menu + parcEventQueue_Printf(session->streamBuffer, "Unrecognized command: %s, displaying help menu\n", cmdline); + _cmd_Help(session, NULL, NULL); + return true; +} + +static void +_cmd_Help(_MetisCommandLineInterface_Session *session, _MetisCommandLineInterface_Command *command, const char *params) +{ + for (int i = 0; _cliCommands[i].text != NULL; i++) { + parcEventQueue_Printf(session->streamBuffer, "%-8s %s\n", _cliCommands[i].text, _cliCommands[i].helpDescription); + } +} + +static void +_cmd_Show(_MetisCommandLineInterface_Session *session, _MetisCommandLineInterface_Command *command, const char *params) +{ + parcEventQueue_Printf(session->streamBuffer, "not implemented\n"); +} + +static void +_cmd_Tunnel(_MetisCommandLineInterface_Session *session, _MetisCommandLineInterface_Command *command, const char *params) +{ + parcEventQueue_Printf(session->streamBuffer, "not implemented\n"); +} + +static void +_cmd_Exit(_MetisCommandLineInterface_Session *session, _MetisCommandLineInterface_Command *command, const char *params) +{ + parcEventQueue_Printf(session->streamBuffer, "Exiting session, goodby\n\n"); + parcEventQueue_Flush(session->streamBuffer, PARCEventType_Write); + _metisCommandLineInterface_RemoveSession(session->parentCli, session); +} + +static void +_cmd_Version(_MetisCommandLineInterface_Session *session, _MetisCommandLineInterface_Command *command, const char *params) +{ + PARCJSON *versionJson = metisConfiguration_GetVersion(metisForwarder_GetConfiguration(session->parentCli->metis)); + char *str = parcJSON_ToString(versionJson); + parcEventQueue_Printf(session->streamBuffer, "%s", str); + parcMemory_Deallocate((void **) &str); + parcJSON_Release(&versionJson); +} |